From 08f11f06abe6f922e07cc7ab277b71083aa42a8c Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Wed, 8 May 2024 13:44:07 -0300 Subject: [PATCH] Revert unnecessary commits from main Issue gh-15016 --- .github/dependabot.template.yml | 41 - .github/dependabot.yml | 207 +- .../dependabot-auto-merge-forward.yml | 57 - .github/workflows/release-scheduler.yml | 2 +- .github/workflows/update-dependabot.yml | 36 - CONTRIBUTING.adoc | 2 +- README.adoc | 4 +- .../aspectj/PostAuthorizeAspectTests.java | 22 +- .../method/aspectj/PostFilterAspectTests.java | 20 +- .../aspectj/PreAuthorizeAspectTests.java | 22 +- .../method/aspectj/PreFilterAspectTests.java | 20 +- .../method/aspectj/SecuredAspectTests.java | 2 +- .../cas/web/CasAuthenticationEntryPoint.java | 18 +- .../cas/web/CasAuthenticationFilter.java | 50 +- ...asGatewayAuthenticationRedirectFilter.java | 126 - .../web/CasGatewayResolverRequestMatcher.java | 67 - .../web/CasAuthenticationEntryPointTests.java | 27 - .../cas/web/CasAuthenticationFilterTests.java | 21 - ...ewayAuthenticationRedirectFilterTests.java | 95 - ...CasGatewayResolverRequestMatcherTests.java | 74 - .../config/SecurityNamespaceHandler.java | 4 +- ...alizeUserDetailsBeanManagerConfigurer.java | 16 +- .../AuthorizationProxyConfiguration.java | 63 - ...erringObservationAuthorizationManager.java | 24 +- ...servationReactiveAuthorizationManager.java | 24 +- .../configuration/EnableMethodSecurity.java | 12 +- .../Jsr250MethodSecurityConfiguration.java | 29 +- .../MethodSecurityAdvisorRegistrar.java | 1 - .../configuration/MethodSecuritySelector.java | 3 +- .../PrePostMethodSecurityConfiguration.java | 144 +- ...ionManagerMethodSecurityConfiguration.java | 115 +- ...activeAuthorizationProxyConfiguration.java | 63 - .../ReactiveMethodSecuritySelector.java | 4 +- .../SecuredMethodSecurityConfiguration.java | 29 +- .../OAuth2ClientConfiguration.java | 30 +- .../AuthorizeHttpRequestsConfigurer.java | 70 +- ...efaultOidcLogoutTokenValidatorFactory.java | 8 +- .../ReactiveOAuth2ClientConfiguration.java | 413 -- .../ReactiveOAuth2ClientImportSelector.java | 101 +- .../ServerHttpSecurityConfiguration.java | 9 - ...Auth2AuthorizedClientManagerRegistrar.java | 30 +- ...artyRegistrationsBeanDefinitionParser.java | 9 +- ...efaultOidcLogoutTokenValidatorFactory.java | 8 +- .../config/web/server/ServerHttpSecurity.java | 434 +- .../web/AuthorizeHttpRequestsDsl.kt | 10 +- .../config/annotation/web/FormLoginDsl.kt | 8 +- .../config/annotation/web/HttpSecurityDsl.kt | 65 +- .../config/annotation/web/Saml2LogoutDsl.kt | 69 - .../annotation/web/saml2/LogoutRequestDsl.kt | 53 - .../annotation/web/saml2/LogoutResponseDsl.kt | 47 - .../web/saml2/Saml2SecurityMarker.kt | 26 - .../web/server/ServerHttpSecurityDsl.kt | 30 - .../web/server/ServerSessionConcurrencyDsl.kt | 48 - .../web/server/ServerSessionManagementDsl.kt | 64 - .../main/resources/META-INF/spring.schemas | 6 +- .../security/config/spring-security-6.3.rnc | 1349 ------ .../security/config/spring-security-6.3.xsd | 3821 ----------------- ...gSecurityCoreVersionSerializableTests.java | 1 + .../AuthorizationProxyConfigurationTests.java | 137 - .../method/configuration/Authz.java | 18 - .../configuration/MethodSecurityService.java | 267 +- .../MethodSecurityServiceConfig.java | 10 - .../MethodSecurityServiceImpl.java | 93 +- .../method/configuration/MyMasker.java | 29 - ...ePostMethodSecurityConfigurationTests.java | 857 +--- ...ctiveMethodSecurityConfigurationTests.java | 215 - ...ctiveMethodSecurityConfigurationTests.java | 298 +- .../ReactiveMethodSecurityService.java | 255 -- .../ReactiveMethodSecurityServiceImpl.java | 91 - .../UserRecordWithEmailProtected.java | 65 - .../issue14637/ApplicationConfig.java | 68 - .../issue14637/Issue14637Tests.java | 50 - .../issue14637/SecurityConfig.java | 29 - .../issue14637/domain/Entry.java | 42 - .../issue14637/repo/EntryRepository.java | 31 - .../HttpSecurityConfigurationTests.java | 90 +- ...orizedClientManagerConfigurationTests.java | 159 +- .../AuthorizeHttpRequestsConfigurerTests.java | 51 - ...orizedClientManagerConfigurationTests.java | 589 --- .../ServerHttpSecurityConfigurationTests.java | 175 - .../config/doc/XsdDocumentedTests.java | 6 +- ...AuthorizedClientManagerRegistrarTests.java | 123 +- ...egistrationsBeanDefinitionParserTests.java | 20 - .../web/server/ServerHttpSecurityTests.java | 10 +- .../server/SessionManagementSpecTests.java | 587 --- .../web/AuthorizeHttpRequestsDslTests.kt | 37 - .../annotation/web/FormLoginDslTests.kt | 47 +- .../annotation/web/Saml2LogoutDslTests.kt | 126 - .../server/ServerSessionManagementDslTests.kt | 282 -- ...zedClientManagerRegistrarTests-clients.xml | 3 - ...dClientManagerRegistrarTests-providers.xml | 13 +- .../security/config/method-security.xml | 2 +- ...efinitionParserTests-MultiRegistration.xml | 10 +- ...finitionParserTests-RelayStateResolver.xml | 58 - ...finitionParserTests-SingleRegistration.xml | 6 +- .../access/expression/ExpressionUtils.java | 2 +- .../SecurityExpressionOperations.java | 6 +- .../hierarchicalroles/RoleHierarchyImpl.java | 176 +- .../access/prepost/PostAuthorize.java | 2 +- .../security/access/prepost/PreAuthorize.java | 2 +- ...rDetailsReactiveAuthenticationManager.java | 27 - .../AuthenticationProvider.java | 8 +- .../CachingUserDetailsService.java | 29 +- ...legatingReactiveAuthenticationManager.java | 33 +- .../dao/DaoAuthenticationProvider.java | 21 +- .../CompromisedPasswordCheckResult.java | 31 - .../password/CompromisedPasswordChecker.java | 37 - .../CompromisedPasswordException.java | 37 - .../ReactiveCompromisedPasswordChecker.java | 36 - .../authorization/AuthorizationDecision.java | 5 +- .../AuthorizationDeniedException.java | 48 - .../authorization/AuthorizationManagers.java | 76 +- .../AuthorizationProxyFactory.java | 40 - .../authorization/AuthorizationResult.java | 32 - .../ObservationAuthorizationManager.java | 23 +- ...servationReactiveAuthorizationManager.java | 23 +- .../AbstractExpressionAttributeRegistry.java | 39 +- .../method/AuthorizationAdvisor.java | 37 - .../AuthorizationAdvisorProxyFactory.java | 542 --- .../method/AuthorizationAnnotationUtils.java | 164 +- .../AuthorizationInterceptorsOrder.java | 6 +- ...rizationManagerAfterMethodInterceptor.java | 37 +- ...ManagerAfterReactiveMethodInterceptor.java | 73 +- ...izationManagerBeforeMethodInterceptor.java | 54 +- ...anagerBeforeReactiveMethodInterceptor.java | 67 +- .../method/AuthorizeReturnObject.java | 41 - ...uthorizeReturnObjectMethodInterceptor.java | 110 - .../authorization/method/ExpressionUtils.java | 51 - .../method/HandleAuthorizationDenied.java | 50 - .../method/Jsr250AuthorizationManager.java | 6 +- .../MethodAuthorizationDeniedHandler.java | 65 - .../PostAuthorizeAuthorizationManager.java | 54 +- .../PostAuthorizeExpressionAttribute.java | 42 - ...tAuthorizeExpressionAttributeRegistry.java | 80 +- ...AuthorizeReactiveAuthorizationManager.java | 47 +- ...tFilterAuthorizationMethodInterceptor.java | 20 +- ...uthorizationReactiveMethodInterceptor.java | 22 +- ...PostFilterExpressionAttributeRegistry.java | 35 +- .../PreAuthorizeAuthorizationManager.java | 37 +- .../PreAuthorizeExpressionAttribute.java | 42 - ...eAuthorizeExpressionAttributeRegistry.java | 84 +- ...AuthorizeReactiveAuthorizationManager.java | 38 +- ...eFilterAuthorizationMethodInterceptor.java | 20 +- ...uthorizationReactiveMethodInterceptor.java | 22 +- .../PreFilterExpressionAttributeRegistry.java | 35 +- .../method/PrePostTemplateDefaults.java | 56 - .../method/ReactiveExpressionUtils.java | 35 +- .../method/SecuredAuthorizationManager.java | 11 +- ...owingMethodAuthorizationDeniedHandler.java | 50 - .../security/core/ComparableVersion.java | 8 +- .../core/SpringSecurityCoreVersion.java | 5 +- .../InMemoryReactiveSessionRegistry.java | 94 - .../session/ReactiveSessionInformation.java | 83 - .../core/session/ReactiveSessionRegistry.java | 67 - .../core/userdetails/UserDetails.java | 16 +- .../security/messages_ca.properties | 4 +- .../security/messages_es_ES.properties | 2 +- .../access/annotation/RequireUserRole.java | 4 +- .../RoleHierarchyImplTests.java | 96 - ...ingReactiveAuthenticationManagerTests.java | 41 +- .../authentication/TestAuthentication.java | 17 +- ...oryReactiveAuthenticationManagerTests.java | 52 - .../dao/DaoAuthenticationProviderTests.java | 52 - ...AuthorizationAdvisorProxyFactoryTests.java | 422 -- .../AuthorizationManagersTests.java | 151 +- ...AuthorizationAdvisorProxyFactoryTests.java | 227 - .../AuthorizationAnnotationUtilsTests.java | 121 +- ...ionManagerAfterMethodInterceptorTests.java | 23 - ...erAfterReactiveMethodInterceptorTests.java | 181 +- ...onManagerBeforeMethodInterceptorTests.java | 21 - ...rBeforeReactiveMethodInterceptorTests.java | 141 +- .../method/ExpressionUtilsTests.java | 70 - .../Jsr250AuthorizationManagerTests.java | 52 +- ...ostAuthorizeAuthorizationManagerTests.java | 25 +- ...erAuthorizationMethodInterceptorTests.java | 30 +- ...PreAuthorizeAuthorizationManagerTests.java | 24 +- ...erAuthorizationMethodInterceptorTests.java | 28 +- .../SecuredAuthorizationManagerTests.java | 24 +- .../core/SpringSecurityCoreVersionTests.java | 4 +- crypto/spring-security-crypto.gradle | 5 +- .../crypto/encrypt/KeyStoreKeyFactory.java | 96 - .../security/crypto/encrypt/RsaAlgorithm.java | 44 - .../security/crypto/encrypt/RsaKeyHelper.java | 284 -- .../security/crypto/encrypt/RsaKeyHolder.java | 27 - .../crypto/encrypt/RsaRawEncryptor.java | 168 - .../crypto/encrypt/RsaSecretEncryptor.java | 247 -- .../encrypt/KeyStoreKeyFactoryTests.java | 70 - .../crypto/encrypt/RsaKeyHelperTests.java | 67 - .../crypto/encrypt/RsaRawEncryptorTests.java | 154 - .../encrypt/RsaSecretEncryptorTests.java | 121 - crypto/src/test/resources/bad.pem | 2 - crypto/src/test/resources/fake.pem | 15 - crypto/src/test/resources/keystore.jks | Bin 3174 -> 0 bytes crypto/src/test/resources/keystore.pkcs12 | Bin 3589 -> 0 bytes crypto/src/test/resources/spacey.pem | 25 - docs/modules/ROOT/nav.adoc | 2 - .../authentication/password-storage.adoc | 96 - .../concurrent-sessions-control.adoc | 458 -- .../pages/reactive/authorization/method.adoc | 9 +- .../oauth2/client/authorization-grants.adoc | 224 +- .../oauth2/client/client-authentication.adoc | 4 +- .../pages/reactive/oauth2/client/core.adoc | 6 +- .../pages/reactive/oauth2/client/index.adoc | 3 +- .../ROOT/pages/reactive/oauth2/index.adoc | 1616 +------ .../pages/reactive/oauth2/login/advanced.adoc | 16 +- .../pages/reactive/oauth2/login/core.adoc | 22 +- .../servlet/appendix/namespace/http.adoc | 6 - .../servlet/appendix/namespace/index.adoc | 2 +- .../authentication/passwords/caching.adoc | 156 - .../passwords/user-details-service.adoc | 2 +- .../servlet/authentication/rememberme.adoc | 2 +- .../servlet/authorization/architecture.adoc | 16 +- .../pages/servlet/authorization/index.adoc | 1 - .../authorization/method-security.adoc | 1176 +---- .../oauth2/client/authorization-grants.adoc | 260 +- .../oauth2/client/client-authentication.adoc | 4 +- .../pages/servlet/oauth2/client/core.adoc | 6 +- .../pages/servlet/oauth2/client/index.adoc | 1 - .../ROOT/pages/servlet/oauth2/index.adoc | 47 +- .../pages/servlet/oauth2/login/advanced.adoc | 22 +- .../ROOT/pages/servlet/oauth2/login/core.adoc | 22 +- .../ROOT/pages/servlet/saml2/logout.adoc | 507 +-- docs/modules/ROOT/pages/whats-new.adoc | 312 +- git/hooks/prepare-forward-merge | 2 +- gradle.properties | 4 +- gradle/libs.versions.toml | 40 +- .../LdapUserDetailsManagerTests.java | 34 - ...veDirectoryLdapAuthenticationProvider.java | 56 +- ...ltActiveDirectoryAuthoritiesPopulator.java | 68 - .../userdetails/LdapUserDetailsManager.java | 14 +- ...urrentSecurityContextArgumentResolver.java | 24 +- ...tSecurityContextArgumentResolverTests.java | 81 - .../client/OAuth2AuthorizedClientId.java | 21 +- ...xchangeOAuth2AuthorizedClientProvider.java | 176 - ...eactiveOAuth2AuthorizedClientProvider.java | 166 - ...faultTokenExchangeTokenResponseClient.java | 126 - .../endpoint/TokenExchangeGrantRequest.java | 78 - ...enExchangeGrantRequestEntityConverter.java | 79 - ...ctiveTokenExchangeTokenResponseClient.java | 83 - .../DefaultOidcIdTokenValidatorFactory.java | 8 +- .../OidcIdTokenDecoderFactory.java | 14 +- .../ReactiveOidcIdTokenDecoderFactory.java | 1 - .../OidcReactiveOAuth2UserService.java | 118 +- .../oidc/userinfo/OidcUserRequestUtils.java | 25 - .../client/oidc/userinfo/OidcUserService.java | 112 +- .../userinfo/DefaultOAuth2UserService.java | 95 +- .../DefaultReactiveOAuth2UserService.java | 35 +- ...th2AuthorizationRequestRedirectFilter.java | 39 +- ...geOAuth2AuthorizedClientProviderTests.java | 384 -- ...veOAuth2AuthorizedClientProviderTests.java | 389 -- ...TokenExchangeTokenResponseClientTests.java | 491 --- ...hangeGrantRequestEntityConverterTests.java | 308 -- .../TokenExchangeGrantRequestTests.java | 91 - ...TokenExchangeTokenResponseClientTests.java | 654 --- ...eactiveOidcIdTokenDecoderFactoryTests.java | 1 - .../OidcReactiveOAuth2UserServiceTests.java | 168 +- .../oidc/userinfo/OidcUserServiceTests.java | 128 +- .../DefaultOAuth2UserServiceTests.java | 48 +- ...DefaultReactiveOAuth2UserServiceTests.java | 48 +- ...thorizationRequestRedirectFilterTests.java | 32 +- .../oauth2/core/AuthorizationGrantType.java | 6 - .../core/ClientAuthenticationMethod.java | 13 +- .../core/endpoint/OAuth2ParameterNames.java | 48 - .../core/ClientAuthenticationMethodTests.java | 13 +- .../spring-security-oauth2-jose.gradle | 3 - .../JwtDecoderProviderConfigurationUtils.java | 12 +- .../security/oauth2/jwt/JwtValidators.java | 56 +- .../X509CertificateThumbprintValidator.java | 136 - .../oauth2/jose/TestX509Certificates.java | 75 - .../oauth2/jose/X509CertificateUtils.java | 165 - .../oauth2/jwt/JwtValidatorsTests.java | 82 - ...09CertificateThumbprintValidatorTests.java | 135 - .../SpringOpaqueTokenIntrospector.java | 80 +- ...SpringReactiveOpaqueTokenIntrospector.java | 71 +- .../SpringOpaqueTokenIntrospectorTests.java | 32 +- ...gReactiveOpaqueTokenIntrospectorTests.java | 30 +- .../service/web/Saml2MetadataFilter.java | 2 +- settings.gradle | 2 +- .../src/main/resources/META-INF/security.tld | 2 +- .../context/showcase/CustomUserDetails.java | 22 +- web/spring-security-web.gradle | 1 - .../security/web/FilterChainProxy.java | 2 +- .../access/IpAddressAuthorizationManager.java | 61 - .../DelegatingAuthenticationConverter.java | 60 - .../HaveIBeenPwnedRestApiPasswordChecker.java | 117 - ...enPwnedRestApiReactivePasswordChecker.java | 111 - .../SessionAuthenticationException.java | 11 +- .../SwitchUserGrantedAuthorityMixIn.java | 46 - .../jackson2/WebServletJackson2Module.java | 16 +- ...urrentSecurityContextArgumentResolver.java | 48 +- ...urrentSecurityContextArgumentResolver.java | 24 +- .../web/server/WebFilterChainProxy.java | 2 +- ...rolServerAuthenticationSuccessHandler.java | 103 - ...legatingServerAuthenticationConverter.java | 72 - ...ingServerAuthenticationSuccessHandler.java | 12 +- ...dServerMaximumSessionsExceededHandler.java | 62 - .../MaximumSessionsContext.java | 59 - ...nServerMaximumSessionsExceededHandler.java | 39 - ...ionServerAuthenticationSuccessHandler.java | 52 - .../ServerMaximumSessionsExceededHandler.java | 38 - ...ReactiveAuthenticationManagerResolver.java | 16 +- .../server/authentication/SessionLimit.java | 50 - .../web/util/matcher/IpAddressMatcher.java | 16 +- ...elegatingAuthenticationConverterTests.java | 135 - ...IBeenPwnedRestApiPasswordCheckerTests.java | 99 - ...edRestApiReactivePasswordCheckerTests.java | 105 - .../SwitchUserGrantedAuthorityMixInTests.java | 77 - ...tSecurityContextArgumentResolverTests.java | 54 +- ...tSecurityContextArgumentResolverTests.java | 61 - ...ingServerAuthenticationConverterTests.java | 137 - ...rverAuthenticationSuccessHandlerTests.java | 16 +- ...rverAuthenticationSuccessHandlerTests.java | 171 - .../InMemoryReactiveSessionRegistryTests.java | 106 - ...erMaximumSessionsExceededHandlerTests.java | 115 - ...erMaximumSessionsExceededHandlerTests.java | 57 - ...rverAuthenticationSuccessHandlerTests.java | 84 - .../util/matcher/IpAddressMatcherTests.java | 6 - 317 files changed, 1264 insertions(+), 30479 deletions(-) delete mode 100644 .github/dependabot.template.yml delete mode 100644 .github/workflows/dependabot-auto-merge-forward.yml delete mode 100644 .github/workflows/update-dependabot.yml delete mode 100644 cas/src/main/java/org/springframework/security/cas/web/CasGatewayAuthenticationRedirectFilter.java delete mode 100644 cas/src/main/java/org/springframework/security/cas/web/CasGatewayResolverRequestMatcher.java delete mode 100644 cas/src/test/java/org/springframework/security/cas/web/CasGatewayAuthenticationRedirectFilterTests.java delete mode 100644 cas/src/test/java/org/springframework/security/cas/web/CasGatewayResolverRequestMatcherTests.java delete mode 100644 config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyConfiguration.java delete mode 100644 config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationProxyConfiguration.java delete mode 100644 config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientConfiguration.java delete mode 100644 config/src/main/kotlin/org/springframework/security/config/annotation/web/Saml2LogoutDsl.kt delete mode 100644 config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutRequestDsl.kt delete mode 100644 config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutResponseDsl.kt delete mode 100644 config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/Saml2SecurityMarker.kt delete mode 100644 config/src/main/kotlin/org/springframework/security/config/web/server/ServerSessionConcurrencyDsl.kt delete mode 100644 config/src/main/kotlin/org/springframework/security/config/web/server/ServerSessionManagementDsl.kt delete mode 100644 config/src/main/resources/org/springframework/security/config/spring-security-6.3.rnc delete mode 100644 config/src/main/resources/org/springframework/security/config/spring-security-6.3.xsd delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyConfigurationTests.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/MyMasker.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/UserRecordWithEmailProtected.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/ApplicationConfig.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/Issue14637Tests.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/SecurityConfig.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/domain/Entry.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/repo/EntryRepository.java delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java delete mode 100644 config/src/test/java/org/springframework/security/config/web/server/SessionManagementSpecTests.java delete mode 100644 config/src/test/kotlin/org/springframework/security/config/annotation/web/Saml2LogoutDslTests.kt delete mode 100644 config/src/test/kotlin/org/springframework/security/config/web/server/ServerSessionManagementDslTests.kt delete mode 100644 config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-RelayStateResolver.xml delete mode 100644 core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordCheckResult.java delete mode 100644 core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordChecker.java delete mode 100644 core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordException.java delete mode 100644 core/src/main/java/org/springframework/security/authentication/password/ReactiveCompromisedPasswordChecker.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/AuthorizationDeniedException.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/AuthorizationProxyFactory.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/AuthorizationResult.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisor.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisorProxyFactory.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/AuthorizeReturnObject.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/AuthorizeReturnObjectMethodInterceptor.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/ExpressionUtils.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedHandler.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttribute.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttribute.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/PrePostTemplateDefaults.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedHandler.java delete mode 100644 core/src/main/java/org/springframework/security/core/session/InMemoryReactiveSessionRegistry.java delete mode 100644 core/src/main/java/org/springframework/security/core/session/ReactiveSessionInformation.java delete mode 100644 core/src/main/java/org/springframework/security/core/session/ReactiveSessionRegistry.java delete mode 100644 core/src/test/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactoryTests.java delete mode 100644 core/src/test/java/org/springframework/security/authorization/ReactiveAuthorizationAdvisorProxyFactoryTests.java delete mode 100644 core/src/test/java/org/springframework/security/authorization/method/ExpressionUtilsTests.java delete mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java delete mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaAlgorithm.java delete mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java delete mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHolder.java delete mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java delete mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java delete mode 100644 crypto/src/test/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactoryTests.java delete mode 100644 crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaKeyHelperTests.java delete mode 100644 crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaRawEncryptorTests.java delete mode 100644 crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptorTests.java delete mode 100644 crypto/src/test/resources/bad.pem delete mode 100644 crypto/src/test/resources/fake.pem delete mode 100644 crypto/src/test/resources/keystore.jks delete mode 100644 crypto/src/test/resources/keystore.pkcs12 delete mode 100644 crypto/src/test/resources/spacey.pem delete mode 100644 docs/modules/ROOT/pages/reactive/authentication/concurrent-sessions-control.adoc delete mode 100644 docs/modules/ROOT/pages/servlet/authentication/passwords/caching.adoc delete mode 100644 ldap/src/main/java/org/springframework/security/ldap/authentication/ad/DefaultActiveDirectoryAuthoritiesPopulator.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeReactiveOAuth2AuthorizedClientProvider.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClient.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverter.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveTokenExchangeTokenResponseClient.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProviderTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/TokenExchangeReactiveOAuth2AuthorizedClientProviderTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClientTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverterTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveTokenExchangeTokenResponseClientTests.java delete mode 100644 oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/X509CertificateThumbprintValidator.java delete mode 100644 oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jose/TestX509Certificates.java delete mode 100644 oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jose/X509CertificateUtils.java delete mode 100644 oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtValidatorsTests.java delete mode 100644 oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/X509CertificateThumbprintValidatorTests.java delete mode 100644 web/src/main/java/org/springframework/security/web/access/IpAddressAuthorizationManager.java delete mode 100644 web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverter.java delete mode 100644 web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordChecker.java delete mode 100644 web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiReactivePasswordChecker.java delete mode 100644 web/src/main/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixIn.java delete mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.java delete mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationConverter.java delete mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/InvalidateLeastUsedServerMaximumSessionsExceededHandler.java delete mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/MaximumSessionsContext.java delete mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/PreventLoginServerMaximumSessionsExceededHandler.java delete mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.java delete mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/ServerMaximumSessionsExceededHandler.java delete mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/SessionLimit.java delete mode 100644 web/src/test/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverterTests.java delete mode 100644 web/src/test/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordCheckerTests.java delete mode 100644 web/src/test/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiReactivePasswordCheckerTests.java delete mode 100644 web/src/test/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixInTests.java delete mode 100644 web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationConverterTests.java delete mode 100644 web/src/test/java/org/springframework/security/web/server/authentication/session/ConcurrentSessionControlServerAuthenticationSuccessHandlerTests.java delete mode 100644 web/src/test/java/org/springframework/security/web/server/authentication/session/InMemoryReactiveSessionRegistryTests.java delete mode 100644 web/src/test/java/org/springframework/security/web/server/authentication/session/InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests.java delete mode 100644 web/src/test/java/org/springframework/security/web/server/authentication/session/PreventLoginServerMaximumSessionsExceededHandlerTests.java delete mode 100644 web/src/test/java/org/springframework/security/web/server/authentication/session/RegisterSessionServerAuthenticationSuccessHandlerTests.java diff --git a/.github/dependabot.template.yml b/.github/dependabot.template.yml deleted file mode 100644 index 20976085e84..00000000000 --- a/.github/dependabot.template.yml +++ /dev/null @@ -1,41 +0,0 @@ -version: 2 - -registries: - spring-milestones: - type: maven-repository - url: https://repo.spring.io/milestone - -updates: - - - package-ecosystem: "gradle" - target-branch: "main" - directory: "/" - schedule: - interval: "daily" - time: "03:00" - timezone: "Etc/UTC" - labels: [ "type: dependency-upgrade" ] - registries: - - "spring-milestones" - ignore: - - dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency - - dependency-name: "org.python:jython" # jython updates break integration tests - - dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes - - dependency-name: "org.junit:junit-bom" - update-types: [ "version-update:semver-major" ] - - dependency-name: "org.mockito:mockito-bom" - update-types: [ "version-update:semver-major" ] - - dependency-name: "*" - update-types: [ "version-update:semver-major", "version-update:semver-minor" ] - - # GitHub Actions - - - package-ecosystem: github-actions - target-branch: "main" - directory: "/" - schedule: - interval: weekly - ignore: - - dependency-name: "sjohnr/*" - - dependency-name: "spring-io/*" - - dependency-name: "spring-security-release-tools/*" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5614f41dfe5..4f9eaa4810a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,161 +1,72 @@ version: 2 + registries: spring-milestones: type: maven-repository url: https://repo.spring.io/milestone + updates: - - package-ecosystem: gradle - target-branch: 5.8.x - directory: / - schedule: - interval: daily - time: '03:00' - timezone: Etc/UTC - labels: - - 'type: dependency-upgrade' - registries: - - spring-milestones - ignore: - - dependency-name: com.nimbusds:nimbus-jose-jwt - - dependency-name: org.python:jython - - dependency-name: org.apache.directory.server:* - - dependency-name: org.junit:junit-bom - update-types: - - version-update:semver-major - - dependency-name: org.mockito:mockito-bom - update-types: - - version-update:semver-major - - dependency-name: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - - package-ecosystem: gradle - target-branch: 6.1.x - directory: / - schedule: - interval: daily - time: '03:00' - timezone: Etc/UTC - labels: - - 'type: dependency-upgrade' - registries: - - spring-milestones - ignore: - - dependency-name: com.nimbusds:nimbus-jose-jwt - - dependency-name: org.python:jython - - dependency-name: org.apache.directory.server:* - - dependency-name: org.junit:junit-bom - update-types: - - version-update:semver-major - - dependency-name: org.mockito:mockito-bom - update-types: - - version-update:semver-major - - dependency-name: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - - package-ecosystem: gradle - target-branch: 6.2.x - directory: / - schedule: - interval: daily - time: '03:00' - timezone: Etc/UTC - labels: - - 'type: dependency-upgrade' - registries: - - spring-milestones - ignore: - - dependency-name: com.nimbusds:nimbus-jose-jwt - - dependency-name: org.python:jython - - dependency-name: org.apache.directory.server:* - - dependency-name: org.junit:junit-bom - update-types: - - version-update:semver-major - - dependency-name: org.mockito:mockito-bom - update-types: - - version-update:semver-major - - dependency-name: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - - package-ecosystem: gradle - target-branch: main - directory: / + + - package-ecosystem: "gradle" + target-branch: "main" + milestone: 319 # 6.2.x + directory: "/" schedule: - interval: daily - time: '03:00' - timezone: Etc/UTC - labels: - - 'type: dependency-upgrade' + interval: "daily" + time: "03:00" + timezone: "Etc/UTC" + labels: [ "type: dependency-upgrade" ] registries: - - spring-milestones - ignore: - - dependency-name: com.nimbusds:nimbus-jose-jwt - - dependency-name: org.python:jython - - dependency-name: org.apache.directory.server:* - - dependency-name: org.junit:junit-bom - update-types: - - version-update:semver-major - - dependency-name: org.mockito:mockito-bom - update-types: - - version-update:semver-major - - dependency-name: com.gradle.enterprise - update-types: - - version-update:semver-major - - version-update:semver-minor - - dependency-name: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - - package-ecosystem: github-actions - target-branch: 5.8.x - directory: / - schedule: - interval: weekly + - "spring-milestones" ignore: - - dependency-name: sjohnr/* - - dependency-name: spring-io/* - - dependency-name: spring-security-release-tools/* - - package-ecosystem: github-actions - target-branch: 6.1.x - directory: / - schedule: - interval: weekly - ignore: - - dependency-name: sjohnr/* - - dependency-name: spring-io/* - - dependency-name: spring-security-release-tools/* - - package-ecosystem: github-actions - target-branch: 6.2.x - directory: / - schedule: - interval: weekly - ignore: - - dependency-name: sjohnr/* - - dependency-name: spring-io/* - - dependency-name: spring-security-release-tools/* - - package-ecosystem: github-actions - target-branch: main - directory: / - schedule: - interval: weekly - ignore: - - dependency-name: sjohnr/* - - dependency-name: spring-io/* - - dependency-name: spring-security-release-tools/* - - package-ecosystem: github-actions - target-branch: docs-build - directory: / + - dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency + - dependency-name: "org.python:jython" # jython updates break integration tests + - dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes + - dependency-name: "org.junit:junit-bom" + update-types: [ "version-update:semver-major" ] + - dependency-name: "org.mockito:mockito-bom" + update-types: [ "version-update:semver-major" ] + - dependency-name: "*" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] + + - package-ecosystem: "gradle" + target-branch: "6.1.x" + milestone: 318 # 6.1.x + directory: "/" schedule: - interval: weekly + interval: "daily" + time: "03:00" + timezone: "Etc/UTC" + labels: [ "type: dependency-upgrade" ] ignore: - - dependency-name: sjohnr/* - - dependency-name: spring-io/* - - dependency-name: spring-security-release-tools/* + - dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency + - dependency-name: "org.python:jython" # jython updates break integration tests + - dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes + - dependency-name: "org.junit:junit-bom" + update-types: [ "version-update:semver-major" ] + - dependency-name: "org.mockito:mockito-bom" + update-types: [ "version-update:semver-major" ] + - dependency-name: "*" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] - - package-ecosystem: npm - target-branch: docs-build - directory: / + - package-ecosystem: "gradle" + target-branch: "5.8.x" + milestone: 246 # 5.8.x + directory: "/" schedule: - interval: weekly + interval: "daily" + time: "03:00" + timezone: "Etc/UTC" + labels: [ "type: dependency-upgrade" ] + ignore: + - dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency + - dependency-name: "org.python:jython" # jython updates break integration tests + - dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes + - dependency-name: "io.mockk:mockk" # mockk updates break tests + - dependency-name: "org.opensaml:*" # org.opensaml maintains two different versions, so it must be updated manually + - dependency-name: "org.junit:junit-bom" + update-types: [ "version-update:semver-major" ] + - dependency-name: "org.mockito:mockito-bom" + update-types: [ "version-update:semver-major" ] + - dependency-name: "*" + update-types: [ "version-update:semver-major", "version-update:semver-minor" ] diff --git a/.github/workflows/dependabot-auto-merge-forward.yml b/.github/workflows/dependabot-auto-merge-forward.yml deleted file mode 100644 index 4989c57aa13..00000000000 --- a/.github/workflows/dependabot-auto-merge-forward.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Auto Merge Forward Dependabot Commits - -on: - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: dependabot-auto-merge-forward - -jobs: - get-supported-branches: - uses: spring-io/spring-security-release-tools/.github/workflows/retrieve-spring-supported-versions.yml@actions-v1 - with: - project: spring-security - type: oss - repository_name: spring-projects/spring-security - - auto-merge-forward-dependabot: - name: Auto Merge Forward Dependabot Commits - runs-on: ubuntu-latest - needs: [get-supported-branches] - permissions: - contents: write - steps: - - name: Checkout - id: checkout - uses: actions/checkout@v4 - with: - token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} - - name: Setup GitHub User - id: setup-gh-user - run: | - git config user.name 'github-actions[bot]' - git config user.email 'github-actions[bot]@users.noreply.github.com' - - name: Run Auto Merge Forward - id: run-auto-merge-forward - uses: spring-io/spring-security-release-tools/.github/actions/auto-merge-forward@actions-v1 - with: - branches: ${{ needs.get-supported-branches.outputs.supported_versions }},main - from-author: dependabot[bot] - notify_result: - name: Check for failures - needs: [ auto-merge-forward-dependabot ] - if: failure() - runs-on: ubuntu-latest - permissions: - actions: read - steps: - - name: Send Slack message - uses: Gamesight/slack-workflow-status@v1.3.0 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }} - channel: '#spring-security-ci' - name: 'CI Notifier' diff --git a/.github/workflows/release-scheduler.yml b/.github/workflows/release-scheduler.yml index 8b9fdce8948..c58ee33e9c7 100644 --- a/.github/workflows/release-scheduler.yml +++ b/.github/workflows/release-scheduler.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: # List of active maintenance branches. - branch: [ main, 6.2.x, 6.1.x, 5.8.x ] + branch: [ main, 6.1.x, 5.8.x ] runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/update-dependabot.yml b/.github/workflows/update-dependabot.yml deleted file mode 100644 index 7b10563ec53..00000000000 --- a/.github/workflows/update-dependabot.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Update dependabot.yml - -on: - workflow_dispatch: - -permissions: - contents: read - -jobs: - - get-supported-branches: - uses: spring-io/spring-security-release-tools/.github/workflows/retrieve-spring-supported-versions.yml@actions-v1 - with: - project: spring-security - type: oss - repository_name: spring-projects/spring-security - - main: - runs-on: ubuntu-latest - needs: [get-supported-branches] - if: ${{ (github.repository == 'spring-projects/spring-security') && (github.ref == 'refs/heads/main') }} - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - uses: spring-io/spring-security-release-tools/.github/actions/generate-dependabot-yml@actions-v1 - name: Update dependabot.yml - with: - gradle-branches: ${{ needs.get-supported-branches.outputs.supported_versions }},main - github-actions-branches: ${{ needs.get-supported-branches.outputs.supported_versions }},main,docs-build - gh-token: ${{ secrets.GITHUB_TOKEN }} - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: Update dependabot.yml diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index e254c3e0567..f9bc67c9efb 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -105,7 +105,7 @@ Once merged, the fix will be forwarded-ported to applicable branches including ` 1. Create a local branch If this is for an issue, consider a branch name with the issue number, like `gh-22276`. [[write-tests]] -1. Add documentation and JUnit Tests for your changes. +1. Add JUnit Tests for your changes [[update-copyright]] 1. In all files you edited, if the copyright header is of the form 2002-20xx, update the final copyright year to the current year. [[add-since]] diff --git a/README.adoc b/README.adoc index 71b556d21e0..5587fac5e85 100644 --- a/README.adoc +++ b/README.adoc @@ -1,8 +1,8 @@ image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge] -image:https://github.com/spring-projects/spring-security/actions/workflows/continuous-integration-workflow.yml/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-security/actions/workflows/continuous-integration-workflow.yml"] +image:https://github.com/spring-projects/spring-security/workflows/CI/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-security/actions?query=workflow%3ACI"] -image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=spring-security"] +image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?search.rootProjectNames=spring-security"] = Spring Security diff --git a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspectTests.java b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspectTests.java index ddc72e9ff90..a58dd4888fe 100644 --- a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspectTests.java +++ b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspectTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -103,13 +103,6 @@ public void denyAllPreAuthorizeDeniesAccess() { assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod); } - @Test - public void nestedDenyAllPostAuthorizeDeniesAccess() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.secured.myObject().denyAllMethod()); - } - interface SecuredInterface { @PostAuthorize("hasRole('X')") @@ -141,10 +134,6 @@ void publicCallsPrivate() { privateMethod(); } - NestedObject myObject() { - return new NestedObject(); - } - } static class SecuredImplSubclass extends SecuredImpl { @@ -168,13 +157,4 @@ void denyAllMethod() { } - static class NestedObject { - - @PostAuthorize("denyAll") - void denyAllMethod() { - - } - - } - } diff --git a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostFilterAspectTests.java b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostFilterAspectTests.java index bf114bae25a..5194e30b38e 100644 --- a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostFilterAspectTests.java +++ b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostFilterAspectTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -54,11 +54,6 @@ public void preFilterMethodWhenListThenFilters() { assertThat(this.prePostSecured.postFilterMethod(objects)).containsExactly("apple", "aubergine"); } - @Test - public void nestedDenyAllPostFilterDeniesAccess() { - assertThat(this.prePostSecured.myObject().denyAllMethod()).isEmpty(); - } - static class PrePostSecured { @PostFilter("filterObject.startsWith('a')") @@ -66,19 +61,6 @@ List postFilterMethod(List objects) { return objects; } - NestedObject myObject() { - return new NestedObject(); - } - - } - - static class NestedObject { - - @PostFilter("filterObject == null") - List denyAllMethod() { - return new ArrayList<>(List.of("deny")); - } - } } diff --git a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspectTests.java b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspectTests.java index f80ad94d633..ce3bd383e98 100644 --- a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspectTests.java +++ b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspectTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -103,13 +103,6 @@ public void denyAllPreAuthorizeDeniesAccess() { assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod); } - @Test - public void nestedDenyAllPreAuthorizeDeniesAccess() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.secured.myObject().denyAllMethod()); - } - interface SecuredInterface { @PreAuthorize("hasRole('X')") @@ -141,10 +134,6 @@ void publicCallsPrivate() { privateMethod(); } - NestedObject myObject() { - return new NestedObject(); - } - } static class SecuredImplSubclass extends SecuredImpl { @@ -168,13 +157,4 @@ void denyAllMethod() { } - static class NestedObject { - - @PreAuthorize("denyAll") - void denyAllMethod() { - - } - - } - } diff --git a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreFilterAspectTests.java b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreFilterAspectTests.java index 9e0ec20c275..81654ac65ce 100644 --- a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreFilterAspectTests.java +++ b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreFilterAspectTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -54,11 +54,6 @@ public void preFilterMethodWhenListThenFilters() { assertThat(this.prePostSecured.preFilterMethod(objects)).containsExactly("apple", "aubergine"); } - @Test - public void nestedDenyAllPreFilterDeniesAccess() { - assertThat(this.prePostSecured.myObject().denyAllMethod(new ArrayList<>(List.of("deny")))).isEmpty(); - } - static class PrePostSecured { @PreFilter("filterObject.startsWith('a')") @@ -66,19 +61,6 @@ List preFilterMethod(List objects) { return objects; } - NestedObject myObject() { - return new NestedObject(); - } - - } - - static class NestedObject { - - @PreFilter("filterObject == null") - List denyAllMethod(List list) { - return list; - } - } } diff --git a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/SecuredAspectTests.java b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/SecuredAspectTests.java index 51ce1b12bde..c175aa72e6f 100644 --- a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/SecuredAspectTests.java +++ b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/SecuredAspectTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java index 5f702ed493a..3f8661614a0 100644 --- a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java +++ b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -28,7 +28,6 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; import org.springframework.util.Assert; /** @@ -62,8 +61,6 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In */ private boolean encodeServiceUrlWithSessionId = true; - private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - @Override public void afterPropertiesSet() { Assert.hasLength(this.loginUrl, "loginUrl must be specified"); @@ -77,7 +74,8 @@ public final void commence(final HttpServletRequest servletRequest, HttpServletR String urlEncodedService = createServiceUrl(servletRequest, response); String redirectUrl = createRedirectUrl(urlEncodedService); preCommence(servletRequest, response); - this.redirectStrategy.sendRedirect(servletRequest, response, redirectUrl); + new DefaultRedirectStrategy().sendRedirect(servletRequest, response, redirectUrl); + // response.sendRedirect(redirectUrl); } /** @@ -151,14 +149,4 @@ protected boolean getEncodeServiceUrlWithSessionId() { return this.encodeServiceUrlWithSessionId; } - /** - * Sets the {@link RedirectStrategy} to use - * @param redirectStrategy the {@link RedirectStrategy} to use - * @since 6.3 - */ - public void setRedirectStrategy(RedirectStrategy redirectStrategy) { - Assert.notNull(redirectStrategy, "redirectStrategy cannot be null"); - this.redirectStrategy = redirectStrategy; - } - } diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java index af2e319f3b4..9e0e08250eb 100644 --- a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java +++ b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java @@ -22,7 +22,6 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage; import org.apereo.cas.client.util.WebUtils; import org.apereo.cas.client.validation.TicketValidator; @@ -41,20 +40,14 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.security.web.savedrequest.HttpSessionRequestCache; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Processes a CAS service ticket, obtains proxy granting tickets, and processes proxy @@ -207,10 +200,6 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); - private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - private RequestCache requestCache = new HttpSessionRequestCache(); - private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); public CasAuthenticationFilter() { @@ -251,22 +240,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ return null; } String serviceTicket = obtainArtifact(request); - if (!StringUtils.hasText(serviceTicket)) { - HttpSession session = request.getSession(false); - if (session != null && session - .getAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR) != null) { - this.logger.debug("Failed authentication response from CAS gateway request"); - session.removeAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR); - SavedRequest savedRequest = this.requestCache.getRequest(request, response); - if (savedRequest != null) { - String redirectUrl = savedRequest.getRedirectUrl(); - this.logger.debug(LogMessage.format("Redirecting to: %s", redirectUrl)); - this.requestCache.removeRequest(request, response); - this.redirectStrategy.sendRedirect(request, response, redirectUrl); - return null; - } - } - + if (serviceTicket == null) { this.logger.debug("Failed to obtain an artifact (cas ticket)"); serviceTicket = ""; } @@ -344,28 +318,6 @@ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy secur this.securityContextHolderStrategy = securityContextHolderStrategy; } - /** - * Set the {@link RedirectStrategy} used to redirect to the saved request if there is - * one saved. Defaults to {@link DefaultRedirectStrategy}. - * @param redirectStrategy the redirect strategy to use - * @since 6.3 - */ - public final void setRedirectStrategy(RedirectStrategy redirectStrategy) { - Assert.notNull(redirectStrategy, "redirectStrategy cannot be null"); - this.redirectStrategy = redirectStrategy; - } - - /** - * The {@link RequestCache} used to retrieve the saved request in failed gateway - * authentication scenarios. - * @param requestCache the request cache to use - * @since 6.3 - */ - public final void setRequestCache(RequestCache requestCache) { - Assert.notNull(requestCache, "requestCache cannot be null"); - this.requestCache = requestCache; - } - /** * Indicates if the request is elgible to process a service ticket. This method exists * for readability. diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasGatewayAuthenticationRedirectFilter.java b/cas/src/main/java/org/springframework/security/cas/web/CasGatewayAuthenticationRedirectFilter.java deleted file mode 100644 index 7dbcbd6b2a7..00000000000 --- a/cas/src/main/java/org/springframework/security/cas/web/CasGatewayAuthenticationRedirectFilter.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2002-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.cas.web; - -import java.io.IOException; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; -import org.apereo.cas.client.util.CommonUtils; -import org.apereo.cas.client.util.WebUtils; - -import org.springframework.security.cas.ServiceProperties; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.savedrequest.HttpSessionRequestCache; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.GenericFilterBean; - -/** - * Redirects the request to the CAS server appending {@code gateway=true} to the URL. Upon - * redirection, the {@link ServiceProperties#isSendRenew()} is ignored and considered as - * {@code false} to align with the specification says that the {@code sendRenew} parameter - * is not compatible with the {@code gateway} parameter. See the CAS - * Protocol Specification for more details. To allow other filters to know if the - * request is a gateway request, this filter creates a session and add an attribute with - * name {@link #CAS_GATEWAY_AUTHENTICATION_ATTR} which can be checked by other filters if - * needed. It is recommended that this filter is placed after - * {@link CasAuthenticationFilter} if it is defined. - * - * @author Michael Remond - * @author Jerome LELEU - * @author Marcus da Coregio - * @since 6.3 - */ -public final class CasGatewayAuthenticationRedirectFilter extends GenericFilterBean { - - public static final String CAS_GATEWAY_AUTHENTICATION_ATTR = "CAS_GATEWAY_AUTHENTICATION"; - - private final String casLoginUrl; - - private final ServiceProperties serviceProperties; - - private RequestMatcher requestMatcher; - - private RequestCache requestCache = new HttpSessionRequestCache(); - - private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - /** - * Constructs a new instance of this class - * @param serviceProperties the {@link ServiceProperties} - */ - public CasGatewayAuthenticationRedirectFilter(String casLoginUrl, ServiceProperties serviceProperties) { - Assert.hasText(casLoginUrl, "casLoginUrl cannot be null or empty"); - Assert.notNull(serviceProperties, "serviceProperties cannot be null"); - this.casLoginUrl = casLoginUrl; - this.serviceProperties = serviceProperties; - this.requestMatcher = new CasGatewayResolverRequestMatcher(this.serviceProperties); - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) - throws IOException, ServletException { - - HttpServletRequest request = (HttpServletRequest) req; - HttpServletResponse response = (HttpServletResponse) res; - - if (!this.requestMatcher.matches(request)) { - chain.doFilter(request, response); - return; - } - - this.requestCache.saveRequest(request, response); - HttpSession session = request.getSession(true); - session.setAttribute(CAS_GATEWAY_AUTHENTICATION_ATTR, true); - String urlEncodedService = WebUtils.constructServiceUrl(request, response, this.serviceProperties.getService(), - null, this.serviceProperties.getServiceParameter(), this.serviceProperties.getArtifactParameter(), - true); - String redirectUrl = CommonUtils.constructRedirectUrl(this.casLoginUrl, - this.serviceProperties.getServiceParameter(), urlEncodedService, false, true); - this.redirectStrategy.sendRedirect(request, response, redirectUrl); - } - - /** - * Sets the {@link RequestMatcher} used to trigger this filter. Defaults to - * {@link CasGatewayResolverRequestMatcher}. - * @param requestMatcher the {@link RequestMatcher} to use - */ - public void setRequestMatcher(RequestMatcher requestMatcher) { - Assert.notNull(requestMatcher, "requestMatcher cannot be null"); - this.requestMatcher = requestMatcher; - } - - /** - * Sets the {@link RequestCache} used to store the current request to be replayed - * after redirect from the CAS server. Defaults to {@link HttpSessionRequestCache}. - * @param requestCache the {@link RequestCache} to use - */ - public void setRequestCache(RequestCache requestCache) { - Assert.notNull(requestCache, "requestCache cannot be null"); - this.requestCache = requestCache; - } - -} diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasGatewayResolverRequestMatcher.java b/cas/src/main/java/org/springframework/security/cas/web/CasGatewayResolverRequestMatcher.java deleted file mode 100644 index 9332ebc136f..00000000000 --- a/cas/src/main/java/org/springframework/security/cas/web/CasGatewayResolverRequestMatcher.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2002-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.cas.web; - -import jakarta.servlet.http.HttpServletRequest; -import org.apereo.cas.client.authentication.DefaultGatewayResolverImpl; -import org.apereo.cas.client.authentication.GatewayResolver; - -import org.springframework.security.cas.ServiceProperties; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * A {@link RequestMatcher} implementation that delegates the check to an instance of - * {@link GatewayResolver}. The request is marked as "gatewayed" using the configured - * {@link GatewayResolver} to avoid infinite loop. - * - * @author Michael Remond - * @author Marcus da Coregio - * @since 6.3 - */ -public final class CasGatewayResolverRequestMatcher implements RequestMatcher { - - private final ServiceProperties serviceProperties; - - private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl(); - - public CasGatewayResolverRequestMatcher(ServiceProperties serviceProperties) { - Assert.notNull(serviceProperties, "serviceProperties cannot be null"); - this.serviceProperties = serviceProperties; - } - - @Override - public boolean matches(HttpServletRequest request) { - boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, this.serviceProperties.getService()); - if (!wasGatewayed) { - this.gatewayStorage.storeGatewayInformation(request, this.serviceProperties.getService()); - return true; - } - return false; - } - - /** - * Sets the {@link GatewayResolver} to check if the request was already gatewayed. - * Defaults to {@link DefaultGatewayResolverImpl} - * @param gatewayStorage the {@link GatewayResolver} to use. Cannot be null. - */ - public void setGatewayStorage(GatewayResolver gatewayStorage) { - Assert.notNull(gatewayStorage, "gatewayStorage cannot be null"); - this.gatewayStorage = gatewayStorage; - } - -} diff --git a/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationEntryPointTests.java b/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationEntryPointTests.java index 4ef20e4b37f..3720bf57181 100644 --- a/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationEntryPointTests.java +++ b/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationEntryPointTests.java @@ -16,22 +16,16 @@ package org.springframework.security.cas.web; -import java.io.IOException; import java.net.URLEncoder; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.cas.ServiceProperties; -import org.springframework.security.web.RedirectStrategy; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; /** * Tests {@link CasAuthenticationEntryPoint}. @@ -101,25 +95,4 @@ public void testNormalOperationWithRenewTrue() throws Exception { .isEqualTo(response.getRedirectedUrl()); } - @Test - void setRedirectStrategyThenUses() throws IOException { - CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint(); - ServiceProperties sp = new ServiceProperties(); - - sp.setService("https://mycompany.com/login/cas"); - ep.setServiceProperties(sp); - ep.setLoginUrl("https://cas/login"); - - RedirectStrategy redirectStrategy = mock(); - - ep.setRedirectStrategy(redirectStrategy); - MockHttpServletRequest req = new MockHttpServletRequest(); - MockHttpServletResponse res = new MockHttpServletResponse(); - - ep.commence(req, res, new BadCredentialsException("bad credentials")); - - verify(redirectStrategy).sendRedirect(eq(req), eq(res), - eq("https://cas/login?service=https%3A%2F%2Fmycompany.com%2Flogin%2Fcas")); - } - } diff --git a/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationFilterTests.java b/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationFilterTests.java index b4227bb9a0d..dffca339f07 100644 --- a/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationFilterTests.java +++ b/cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationFilterTests.java @@ -20,7 +20,6 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpSession; import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -42,7 +41,6 @@ import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -226,25 +224,6 @@ public void successfulAuthenticationWhenProxyRequestThenSavesSecurityContext() t verify(securityContextRepository).saveContext(any(SecurityContext.class), eq(request), eq(response)); } - @Test - public void attemptAuthenticationWhenNoServiceTicketAndIsGatewayRequestThenRedirectToSavedRequestAndClearAttribute() - throws Exception { - CasAuthenticationFilter filter = new CasAuthenticationFilter(); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - HttpSession session = request.getSession(true); - session.setAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR, true); - - new HttpSessionRequestCache().saveRequest(request, response); - - Authentication authn = filter.attemptAuthentication(request, response); - assertThat(authn).isNull(); - assertThat(response.getStatus()).isEqualTo(302); - assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost?continue"); - assertThat(session.getAttribute(CasGatewayAuthenticationRedirectFilter.CAS_GATEWAY_AUTHENTICATION_ATTR)) - .isNull(); - } - @Test void successfulAuthenticationWhenSecurityContextRepositorySetThenUses() throws ServletException, IOException { SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class); diff --git a/cas/src/test/java/org/springframework/security/cas/web/CasGatewayAuthenticationRedirectFilterTests.java b/cas/src/test/java/org/springframework/security/cas/web/CasGatewayAuthenticationRedirectFilterTests.java deleted file mode 100644 index fd7af4a8711..00000000000 --- a/cas/src/test/java/org/springframework/security/cas/web/CasGatewayAuthenticationRedirectFilterTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2002-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.cas.web; - -import java.io.IOException; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.cas.ServiceProperties; -import org.springframework.security.web.savedrequest.RequestCache; - -import static org.assertj.core.api.Assertions.assertThat; -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 CasGatewayAuthenticationRedirectFilter}. - * - * @author Jerome LELEU - * @author Marcus da Coregio - */ -public class CasGatewayAuthenticationRedirectFilterTests { - - private static final String CAS_LOGIN_URL = "http://mycasserver/login"; - - CasGatewayAuthenticationRedirectFilter filter = new CasGatewayAuthenticationRedirectFilter(CAS_LOGIN_URL, - serviceProperties()); - - @Test - void doFilterWhenMatchesThenSavesRequestAndSavesAttributeAndSendRedirect() throws IOException, ServletException { - RequestCache requestCache = mock(); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.filter.setRequestMatcher((req) -> true); - this.filter.setRequestCache(requestCache); - this.filter.doFilter(request, response, new MockFilterChain()); - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getHeader("Location")) - .isEqualTo("http://mycasserver/login?service=http%3A%2F%2Flocalhost%2Flogin%2Fcas&gateway=true"); - verify(requestCache).saveRequest(request, response); - } - - @Test - void doFilterWhenNotMatchThenContinueFilter() throws ServletException, IOException { - this.filter.setRequestMatcher((req) -> false); - FilterChain chain = mock(); - MockHttpServletResponse response = mock(); - this.filter.doFilter(new MockHttpServletRequest(), response, chain); - verify(chain).doFilter(any(), any()); - verifyNoInteractions(response); - } - - @Test - void doFilterWhenSendRenewTrueThenIgnores() throws ServletException, IOException { - ServiceProperties serviceProperties = serviceProperties(); - serviceProperties.setSendRenew(true); - this.filter = new CasGatewayAuthenticationRedirectFilter(CAS_LOGIN_URL, serviceProperties); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.filter.setRequestMatcher((req) -> true); - this.filter.doFilter(request, response, new MockFilterChain()); - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getHeader("Location")) - .isEqualTo("http://mycasserver/login?service=http%3A%2F%2Flocalhost%2Flogin%2Fcas&gateway=true"); - } - - private static ServiceProperties serviceProperties() { - ServiceProperties serviceProperties = new ServiceProperties(); - serviceProperties.setService("http://localhost/login/cas"); - return serviceProperties; - } - -} diff --git a/cas/src/test/java/org/springframework/security/cas/web/CasGatewayResolverRequestMatcherTests.java b/cas/src/test/java/org/springframework/security/cas/web/CasGatewayResolverRequestMatcherTests.java deleted file mode 100644 index 97a590c068c..00000000000 --- a/cas/src/test/java/org/springframework/security/cas/web/CasGatewayResolverRequestMatcherTests.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2002-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.cas.web; - -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.cas.ServiceProperties; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link CasGatewayResolverRequestMatcher}. - * - * @author Marcus da Coregio - */ -class CasGatewayResolverRequestMatcherTests { - - CasGatewayResolverRequestMatcher matcher = new CasGatewayResolverRequestMatcher(new ServiceProperties()); - - @Test - void constructorWhenServicePropertiesNullThenException() { - assertThatIllegalArgumentException().isThrownBy(() -> new CasGatewayResolverRequestMatcher(null)) - .withMessage("serviceProperties cannot be null"); - } - - @Test - void matchesWhenAlreadyGatewayedThenReturnsFalse() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.getSession().setAttribute("_const_cas_gateway_", "yes"); - boolean matches = this.matcher.matches(request); - assertThat(matches).isFalse(); - } - - @Test - void matchesWhenNotGatewayedThenReturnsTrue() { - MockHttpServletRequest request = new MockHttpServletRequest(); - boolean matches = this.matcher.matches(request); - assertThat(matches).isTrue(); - } - - @Test - void matchesWhenNoSessionThenReturnsTrue() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setSession(null); - boolean matches = this.matcher.matches(request); - assertThat(matches).isTrue(); - } - - @Test - void matchesWhenNotGatewayedAndCheckedAgainThenSavesAsGatewayedAndReturnsFalse() { - MockHttpServletRequest request = new MockHttpServletRequest(); - boolean matches = this.matcher.matches(request); - boolean secondMatch = this.matcher.matches(request); - assertThat(matches).isTrue(); - assertThat(secondMatch).isFalse(); - } - -} diff --git a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java index 260b46a4cd7..a1921249fb9 100644 --- a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java +++ b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java @@ -96,7 +96,7 @@ public BeanDefinition parse(Element element, ParserContext pc) { pc.getReaderContext() .fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or " + "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema " - + "with Spring Security 6.3. Please update your schema declarations to the 6.3 schema.", + + "with Spring Security 6.2. Please update your schema declarations to the 6.2 schema.", element); } String name = pc.getDelegate().getLocalName(element); @@ -221,7 +221,7 @@ private boolean namespaceMatchesVersion(Element element) { private boolean matchesVersionInternal(Element element) { String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"); - return schemaLocation.matches("(?m).*spring-security-6\\.3.*.xsd.*") + return schemaLocation.matches("(?m).*spring-security-6\\.2.*.xsd.*") || schemaLocation.matches("(?m).*spring-security.xsd.*") || !schemaLocation.matches("(?m).*spring-security.*"); } diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeUserDetailsBeanManagerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeUserDetailsBeanManagerConfigurer.java index 621b53f9eeb..07fff8886ba 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeUserDetailsBeanManagerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeUserDetailsBeanManagerConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.authentication.password.CompromisedPasswordChecker; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.userdetails.UserDetailsPasswordService; import org.springframework.security.core.userdetails.UserDetailsService; @@ -66,21 +65,14 @@ public void configure(AuthenticationManagerBuilder auth) throws Exception { } PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class); UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class); - CompromisedPasswordChecker passwordChecker = getBeanOrNull(CompromisedPasswordChecker.class); - DaoAuthenticationProvider provider; + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(userDetailsService); if (passwordEncoder != null) { - provider = new DaoAuthenticationProvider(passwordEncoder); - } - else { - provider = new DaoAuthenticationProvider(); + provider.setPasswordEncoder(passwordEncoder); } - provider.setUserDetailsService(userDetailsService); if (passwordManager != null) { provider.setUserDetailsPasswordService(passwordManager); } - if (passwordChecker != null) { - provider.setCompromisedPasswordChecker(passwordChecker); - } provider.afterPropertiesSet(); auth.authenticationProvider(provider); } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyConfiguration.java deleted file mode 100644 index a4e29505a74..00000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyConfiguration.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration; - -import java.util.ArrayList; -import java.util.List; - -import org.aopalliance.intercept.MethodInterceptor; - -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Role; -import org.springframework.security.authorization.method.AuthorizationAdvisor; -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; -import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor; -import org.springframework.security.config.Customizer; - -@Configuration(proxyBeanMethods = false) -final class AuthorizationProxyConfiguration implements AopInfrastructureBean { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider provider, - ObjectProvider> customizers) { - List advisors = new ArrayList<>(); - provider.forEach(advisors::add); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - customizers.forEach((c) -> c.customize(factory)); - factory.setAdvisors(advisors); - return factory; - } - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static MethodInterceptor authorizeReturnObjectMethodInterceptor(ObjectProvider provider, - AuthorizationAdvisorProxyFactory authorizationProxyFactory) { - AuthorizeReturnObjectMethodInterceptor interceptor = new AuthorizeReturnObjectMethodInterceptor( - authorizationProxyFactory); - List advisors = new ArrayList<>(); - provider.forEach(advisors::add); - advisors.add(interceptor); - authorizationProxyFactory.setAdvisors(advisors); - return interceptor; - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java index 3328531606e..4d534e5cfb4 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java @@ -19,26 +19,18 @@ import java.util.function.Supplier; import io.micrometer.observation.ObservationRegistry; -import org.aopalliance.intercept.MethodInvocation; import org.springframework.beans.factory.ObjectProvider; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ObservationAuthorizationManager; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodInvocationResult; -import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; import org.springframework.security.core.Authentication; import org.springframework.util.function.SingletonSupplier; -final class DeferringObservationAuthorizationManager - implements AuthorizationManager, MethodAuthorizationDeniedHandler { +final class DeferringObservationAuthorizationManager implements AuthorizationManager { private final Supplier> delegate; - private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - DeferringObservationAuthorizationManager(ObjectProvider provider, AuthorizationManager delegate) { this.delegate = SingletonSupplier.of(() -> { @@ -48,9 +40,6 @@ final class DeferringObservationAuthorizationManager } return new ObservationAuthorizationManager<>(registry, delegate); }); - if (delegate instanceof MethodAuthorizationDeniedHandler h) { - this.handler = h; - } } @Override @@ -58,15 +47,4 @@ public AuthorizationDecision check(Supplier authentication, T ob return this.delegate.get().check(authentication, object); } - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult); - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, - AuthorizationResult authorizationResult) { - return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult); - } - } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java index 8b028a70772..9061cb64bb7 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java @@ -19,27 +19,19 @@ import java.util.function.Supplier; import io.micrometer.observation.ObservationRegistry; -import org.aopalliance.intercept.MethodInvocation; import reactor.core.publisher.Mono; import org.springframework.beans.factory.ObjectProvider; import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ObservationReactiveAuthorizationManager; import org.springframework.security.authorization.ReactiveAuthorizationManager; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodInvocationResult; -import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; import org.springframework.security.core.Authentication; import org.springframework.util.function.SingletonSupplier; -final class DeferringObservationReactiveAuthorizationManager - implements ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { +final class DeferringObservationReactiveAuthorizationManager implements ReactiveAuthorizationManager { private final Supplier> delegate; - private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - DeferringObservationReactiveAuthorizationManager(ObjectProvider provider, ReactiveAuthorizationManager delegate) { this.delegate = SingletonSupplier.of(() -> { @@ -49,9 +41,6 @@ final class DeferringObservationReactiveAuthorizationManager } return new ObservationReactiveAuthorizationManager<>(registry, delegate); }); - if (delegate instanceof MethodAuthorizationDeniedHandler h) { - this.handler = h; - } } @Override @@ -59,15 +48,4 @@ public Mono check(Mono authentication, T return this.delegate.get().check(authentication, object); } - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult); - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, - AuthorizationResult authorizationResult) { - return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult); - } - } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java index 919de3e4096..b65f3369db7 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -87,14 +87,4 @@ */ AdviceMode mode() default AdviceMode.PROXY; - /** - * Indicate additional offset in the ordering of the execution of the security - * interceptors when multiple advices are applied at a specific joinpoint. I.e., - * precedence of each security interceptor enabled by this annotation will be - * calculated as sum of its default precedence and offset. The default is 0. - * @return the offset in the order the security advisor should be applied - * @since 6.3 - */ - int offset() default 0; - } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java index 45908fb549a..667eb7e5250 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -20,18 +20,11 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; -import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportAware; import org.springframework.context.annotation.Role; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.authorization.AuthoritiesAuthorizationManager; -import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.authorization.method.Jsr250AuthorizationManager; @@ -49,23 +42,15 @@ */ @Configuration(proxyBeanMethods = false) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) -final class Jsr250MethodSecurityConfiguration implements ImportAware, AopInfrastructureBean { - - private int interceptorOrderOffset; +final class Jsr250MethodSecurityConfiguration { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor jsr250AuthorizationMethodInterceptor( ObjectProvider defaultsProvider, ObjectProvider strategyProvider, - ObjectProvider eventPublisherProvider, - ObjectProvider registryProvider, ObjectProvider roleHierarchyProvider, - Jsr250MethodSecurityConfiguration configuration) { + ObjectProvider registryProvider) { Jsr250AuthorizationManager jsr250 = new Jsr250AuthorizationManager(); - AuthoritiesAuthorizationManager authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager(); - RoleHierarchy roleHierarchy = roleHierarchyProvider.getIfAvailable(NullRoleHierarchy::new); - authoritiesAuthorizationManager.setRoleHierarchy(roleHierarchy); - jsr250.setAuthoritiesAuthorizationManager(authoritiesAuthorizationManager); defaultsProvider.ifAvailable((d) -> jsr250.setRolePrefix(d.getRolePrefix())); SecurityContextHolderStrategy strategy = strategyProvider .getIfAvailable(SecurityContextHolder::getContextHolderStrategy); @@ -73,16 +58,8 @@ static MethodInterceptor jsr250AuthorizationMethodInterceptor( registryProvider, jsr250); AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor .jsr250(manager); - interceptor.setOrder(interceptor.getOrder() + configuration.interceptorOrderOffset); interceptor.setSecurityContextHolderStrategy(strategy); - eventPublisherProvider.ifAvailable(interceptor::setAuthorizationEventPublisher); return interceptor; } - @Override - public void setImportMetadata(AnnotationMetadata importMetadata) { - EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize(); - this.interceptorOrderOffset = annotation.offset(); - } - } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityAdvisorRegistrar.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityAdvisorRegistrar.java index a5f82428947..409f6fa1ea2 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityAdvisorRegistrar.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityAdvisorRegistrar.java @@ -33,7 +33,6 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B registerAsAdvisor("postAuthorizeAuthorization", registry); registerAsAdvisor("securedAuthorization", registry); registerAsAdvisor("jsr250Authorization", registry); - registerAsAdvisor("authorizeReturnObject", registry); } private void registerAsAdvisor(String prefix, BeanDefinitionRegistry registry) { diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java index f8f5c8f1723..4b561360a73 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -56,7 +56,6 @@ public String[] selectImports(@NonNull AnnotationMetadata importMetadata) { if (annotation.jsr250Enabled()) { imports.add(Jsr250MethodSecurityConfiguration.class.getName()); } - imports.add(AuthorizationProxyConfiguration.class.getName()); return imports.toArray(new String[0]); } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java index 1b3d4c1bfae..79133e62a4d 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,41 +16,33 @@ package org.springframework.security.config.annotation.method.configuration; -import java.util.function.Consumer; import java.util.function.Supplier; import io.micrometer.observation.ObservationRegistry; -import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.springframework.aop.Pointcut; -import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportAware; import org.springframework.context.annotation.Role; -import org.springframework.core.type.AnnotationMetadata; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; -import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.method.AuthorizationAdvisor; import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager; import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor; import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager; import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor; -import org.springframework.security.authorization.method.PrePostTemplateDefaults; import org.springframework.security.config.core.GrantedAuthorityDefaults; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.util.function.SingletonSupplier; @@ -64,102 +56,73 @@ */ @Configuration(proxyBeanMethods = false) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) -final class PrePostMethodSecurityConfiguration implements ImportAware, AopInfrastructureBean { - - private int interceptorOrderOffset; +final class PrePostMethodSecurityConfiguration { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor preFilterAuthorizationMethodInterceptor( ObjectProvider defaultsProvider, - ObjectProvider methodSecurityDefaultsProvider, ObjectProvider expressionHandlerProvider, - ObjectProvider strategyProvider, - ObjectProvider roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration, - ApplicationContext context) { + ObjectProvider strategyProvider, ApplicationContext context) { PreFilterAuthorizationMethodInterceptor preFilter = new PreFilterAuthorizationMethodInterceptor(); - preFilter.setOrder(preFilter.getOrder() + configuration.interceptorOrderOffset); - return new DeferringMethodInterceptor<>(preFilter, (f) -> { - methodSecurityDefaultsProvider.ifAvailable(f::setTemplateDefaults); - f.setExpressionHandler(expressionHandlerProvider - .getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, roleHierarchyProvider, context))); - strategyProvider.ifAvailable(f::setSecurityContextHolderStrategy); - }); + strategyProvider.ifAvailable(preFilter::setSecurityContextHolderStrategy); + preFilter.setExpressionHandler( + new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context)); + return preFilter; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor( ObjectProvider defaultsProvider, - ObjectProvider methodSecurityDefaultsProvider, ObjectProvider expressionHandlerProvider, ObjectProvider strategyProvider, ObjectProvider eventPublisherProvider, - ObjectProvider registryProvider, ObjectProvider roleHierarchyProvider, - PrePostMethodSecurityConfiguration configuration, ApplicationContext context) { + ObjectProvider registryProvider, ApplicationContext context) { PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); - manager.setApplicationContext(context); + manager.setExpressionHandler( + new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context)); AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor .preAuthorize(manager(manager, registryProvider)); - preAuthorize.setOrder(preAuthorize.getOrder() + configuration.interceptorOrderOffset); - return new DeferringMethodInterceptor<>(preAuthorize, (f) -> { - methodSecurityDefaultsProvider.ifAvailable(manager::setTemplateDefaults); - manager.setExpressionHandler(expressionHandlerProvider - .getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, roleHierarchyProvider, context))); - strategyProvider.ifAvailable(f::setSecurityContextHolderStrategy); - eventPublisherProvider.ifAvailable(f::setAuthorizationEventPublisher); - }); + strategyProvider.ifAvailable(preAuthorize::setSecurityContextHolderStrategy); + eventPublisherProvider.ifAvailable(preAuthorize::setAuthorizationEventPublisher); + return preAuthorize; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor( ObjectProvider defaultsProvider, - ObjectProvider methodSecurityDefaultsProvider, ObjectProvider expressionHandlerProvider, ObjectProvider strategyProvider, ObjectProvider eventPublisherProvider, - ObjectProvider registryProvider, ObjectProvider roleHierarchyProvider, - PrePostMethodSecurityConfiguration configuration, ApplicationContext context) { + ObjectProvider registryProvider, ApplicationContext context) { PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); - manager.setApplicationContext(context); + manager.setExpressionHandler( + new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context)); AuthorizationManagerAfterMethodInterceptor postAuthorize = AuthorizationManagerAfterMethodInterceptor .postAuthorize(manager(manager, registryProvider)); - postAuthorize.setOrder(postAuthorize.getOrder() + configuration.interceptorOrderOffset); - return new DeferringMethodInterceptor<>(postAuthorize, (f) -> { - methodSecurityDefaultsProvider.ifAvailable(manager::setTemplateDefaults); - manager.setExpressionHandler(expressionHandlerProvider - .getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, roleHierarchyProvider, context))); - strategyProvider.ifAvailable(f::setSecurityContextHolderStrategy); - eventPublisherProvider.ifAvailable(f::setAuthorizationEventPublisher); - }); + strategyProvider.ifAvailable(postAuthorize::setSecurityContextHolderStrategy); + eventPublisherProvider.ifAvailable(postAuthorize::setAuthorizationEventPublisher); + return postAuthorize; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor postFilterAuthorizationMethodInterceptor( ObjectProvider defaultsProvider, - ObjectProvider methodSecurityDefaultsProvider, ObjectProvider expressionHandlerProvider, - ObjectProvider strategyProvider, - ObjectProvider roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration, - ApplicationContext context) { + ObjectProvider strategyProvider, ApplicationContext context) { PostFilterAuthorizationMethodInterceptor postFilter = new PostFilterAuthorizationMethodInterceptor(); - postFilter.setOrder(postFilter.getOrder() + configuration.interceptorOrderOffset); - return new DeferringMethodInterceptor<>(postFilter, (f) -> { - methodSecurityDefaultsProvider.ifAvailable(f::setTemplateDefaults); - f.setExpressionHandler(expressionHandlerProvider - .getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, roleHierarchyProvider, context))); - strategyProvider.ifAvailable(f::setSecurityContextHolderStrategy); - }); + strategyProvider.ifAvailable(postFilter::setSecurityContextHolderStrategy); + postFilter.setExpressionHandler( + new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context)); + return postFilter; } private static MethodSecurityExpressionHandler defaultExpressionHandler( - ObjectProvider defaultsProvider, - ObjectProvider roleHierarchyProvider, ApplicationContext context) { + ObjectProvider defaultsProvider, ApplicationContext context) { DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler(); - RoleHierarchy roleHierarchy = roleHierarchyProvider.getIfAvailable(NullRoleHierarchy::new); - handler.setRoleHierarchy(roleHierarchy); defaultsProvider.ifAvailable((d) -> handler.setDefaultRolePrefix(d.getRolePrefix())); handler.setApplicationContext(context); return handler; @@ -170,54 +133,41 @@ static AuthorizationManager manager(AuthorizationManager delegate, return new DeferringObservationAuthorizationManager<>(registryProvider, delegate); } - @Override - public void setImportMetadata(AnnotationMetadata importMetadata) { - EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize(); - this.interceptorOrderOffset = annotation.offset(); - } - - private static final class DeferringMethodInterceptor - implements AuthorizationAdvisor { - - private final Pointcut pointcut; - - private final int order; + private static final class DeferringMethodSecurityExpressionHandler implements MethodSecurityExpressionHandler { - private final Supplier delegate; + private final Supplier expressionHandler; - DeferringMethodInterceptor(M delegate, Consumer supplier) { - this.pointcut = delegate.getPointcut(); - this.order = delegate.getOrder(); - this.delegate = SingletonSupplier.of(() -> { - supplier.accept(delegate); - return delegate; - }); + private DeferringMethodSecurityExpressionHandler( + ObjectProvider expressionHandlerProvider, + ObjectProvider defaultsProvider, ApplicationContext applicationContext) { + this.expressionHandler = SingletonSupplier.of(() -> expressionHandlerProvider + .getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, applicationContext))); } - @Nullable @Override - public Object invoke(@NotNull MethodInvocation invocation) throws Throwable { - return this.delegate.get().invoke(invocation); + public ExpressionParser getExpressionParser() { + return this.expressionHandler.get().getExpressionParser(); } @Override - public Pointcut getPointcut() { - return this.pointcut; + public EvaluationContext createEvaluationContext(Authentication authentication, MethodInvocation invocation) { + return this.expressionHandler.get().createEvaluationContext(authentication, invocation); } @Override - public Advice getAdvice() { - return this; + public EvaluationContext createEvaluationContext(Supplier authentication, + MethodInvocation invocation) { + return this.expressionHandler.get().createEvaluationContext(authentication, invocation); } @Override - public int getOrder() { - return this.order; + public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) { + return this.expressionHandler.get().filter(filterTarget, filterExpression, ctx); } @Override - public boolean isPerInstance() { - return true; + public void setReturnObject(Object returnObject, EvaluationContext ctx) { + this.expressionHandler.get().setReturnObject(returnObject, ctx); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java index 8e777e8bc4c..4f53005f7c8 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,22 +16,12 @@ package org.springframework.security.config.annotation.method.configuration; -import java.util.function.Consumer; -import java.util.function.Supplier; - import io.micrometer.observation.ObservationRegistry; -import org.aopalliance.aop.Advice; -import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.springframework.aop.Pointcut; -import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Role; @@ -39,7 +29,6 @@ import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authorization.ReactiveAuthorizationManager; -import org.springframework.security.authorization.method.AuthorizationAdvisor; import org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor; import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor; import org.springframework.security.authorization.method.MethodInvocationResult; @@ -47,9 +36,7 @@ import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor; import org.springframework.security.authorization.method.PreAuthorizeReactiveAuthorizationManager; import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor; -import org.springframework.security.authorization.method.PrePostTemplateDefaults; import org.springframework.security.config.core.GrantedAuthorityDefaults; -import org.springframework.util.function.SingletonSupplier; /** * Configuration for a {@link ReactiveAuthenticationManager} based Method Security. @@ -58,58 +45,38 @@ * @since 5.8 */ @Configuration(proxyBeanMethods = false) -final class ReactiveAuthorizationManagerMethodSecurityConfiguration implements AopInfrastructureBean { +final class ReactiveAuthorizationManagerMethodSecurityConfiguration { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static MethodInterceptor preFilterAuthorizationMethodInterceptor(MethodSecurityExpressionHandler expressionHandler, - ObjectProvider defaultsObjectProvider) { - PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor( - expressionHandler); - return new DeferringMethodInterceptor<>(interceptor, - (i) -> defaultsObjectProvider.ifAvailable(i::setTemplateDefaults)); + static PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor( + MethodSecurityExpressionHandler expressionHandler) { + return new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor( - MethodSecurityExpressionHandler expressionHandler, - ObjectProvider defaultsObjectProvider, - ObjectProvider registryProvider, ApplicationContext context) { - PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager( - expressionHandler); - manager.setApplicationContext(context); - ReactiveAuthorizationManager authorizationManager = manager(manager, registryProvider); - AuthorizationAdvisor interceptor = AuthorizationManagerBeforeReactiveMethodInterceptor - .preAuthorize(authorizationManager); - return new DeferringMethodInterceptor<>(interceptor, - (i) -> defaultsObjectProvider.ifAvailable(manager::setTemplateDefaults)); + static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor( + MethodSecurityExpressionHandler expressionHandler, ObjectProvider registryProvider) { + ReactiveAuthorizationManager authorizationManager = manager( + new PreAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider); + return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static MethodInterceptor postFilterAuthorizationMethodInterceptor(MethodSecurityExpressionHandler expressionHandler, - ObjectProvider defaultsObjectProvider) { - PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor( - expressionHandler); - return new DeferringMethodInterceptor<>(interceptor, - (i) -> defaultsObjectProvider.ifAvailable(i::setTemplateDefaults)); + static PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor( + MethodSecurityExpressionHandler expressionHandler) { + return new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor( - MethodSecurityExpressionHandler expressionHandler, - ObjectProvider defaultsObjectProvider, - ObjectProvider registryProvider, ApplicationContext context) { - PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager( - expressionHandler); - manager.setApplicationContext(context); - ReactiveAuthorizationManager authorizationManager = manager(manager, registryProvider); - AuthorizationAdvisor interceptor = AuthorizationManagerAfterReactiveMethodInterceptor - .postAuthorize(authorizationManager); - return new DeferringMethodInterceptor<>(interceptor, - (i) -> defaultsObjectProvider.ifAvailable(manager::setTemplateDefaults)); + static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor( + MethodSecurityExpressionHandler expressionHandler, ObjectProvider registryProvider) { + ReactiveAuthorizationManager authorizationManager = manager( + new PostAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider); + return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager); } @Bean @@ -128,50 +95,4 @@ static ReactiveAuthorizationManager manager(ReactiveAuthorizationManager< return new DeferringObservationReactiveAuthorizationManager<>(registryProvider, delegate); } - private static final class DeferringMethodInterceptor - implements AuthorizationAdvisor { - - private final Pointcut pointcut; - - private final int order; - - private final Supplier delegate; - - DeferringMethodInterceptor(M delegate, Consumer supplier) { - this.pointcut = delegate.getPointcut(); - this.order = delegate.getOrder(); - this.delegate = SingletonSupplier.of(() -> { - supplier.accept(delegate); - return delegate; - }); - } - - @Nullable - @Override - public Object invoke(@NotNull MethodInvocation invocation) throws Throwable { - return this.delegate.get().invoke(invocation); - } - - @Override - public Pointcut getPointcut() { - return this.pointcut; - } - - @Override - public Advice getAdvice() { - return this; - } - - @Override - public int getOrder() { - return this.order; - } - - @Override - public boolean isPerInstance() { - return true; - } - - } - } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationProxyConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationProxyConfiguration.java deleted file mode 100644 index 7912991c4fb..00000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationProxyConfiguration.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration; - -import java.util.ArrayList; -import java.util.List; - -import org.aopalliance.intercept.MethodInterceptor; - -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Role; -import org.springframework.security.authorization.method.AuthorizationAdvisor; -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; -import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor; -import org.springframework.security.config.Customizer; - -@Configuration(proxyBeanMethods = false) -final class ReactiveAuthorizationProxyConfiguration implements AopInfrastructureBean { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider provider, - ObjectProvider> customizers) { - List advisors = new ArrayList<>(); - provider.forEach(advisors::add); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults(); - customizers.forEach((c) -> c.customize(factory)); - factory.setAdvisors(advisors); - return factory; - } - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static MethodInterceptor authorizeReturnObjectMethodInterceptor(ObjectProvider provider, - AuthorizationAdvisorProxyFactory authorizationProxyFactory) { - AuthorizeReturnObjectMethodInterceptor interceptor = new AuthorizeReturnObjectMethodInterceptor( - authorizationProxyFactory); - List advisors = new ArrayList<>(); - provider.forEach(advisors::add); - advisors.add(interceptor); - authorizationProxyFactory.setAdvisors(advisors); - return interceptor; - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java index b1c923383e5..dd984bc1a8f 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java @@ -51,15 +51,13 @@ public String[] selectImports(AnnotationMetadata importMetadata) { else { imports.add(ReactiveMethodSecurityConfiguration.class.getName()); } - imports.add(ReactiveAuthorizationProxyConfiguration.class.getName()); return imports.toArray(new String[0]); } private static final class AutoProxyRegistrarSelector extends AdviceModeImportSelector { - private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName(), - MethodSecurityAdvisorRegistrar.class.getName() }; + private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName() }; @Override protected String[] selectImports(@NonNull AdviceMode adviceMode) { diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java index 2b6a2e29280..a2f565f117d 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -20,19 +20,12 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; -import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportAware; import org.springframework.context.annotation.Role; -import org.springframework.core.type.AnnotationMetadata; import org.springframework.security.access.annotation.Secured; -import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.authorization.AuthoritiesAuthorizationManager; -import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.authorization.method.SecuredAuthorizationManager; @@ -49,38 +42,22 @@ */ @Configuration(proxyBeanMethods = false) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) -final class SecuredMethodSecurityConfiguration implements ImportAware, AopInfrastructureBean { - - private int interceptorOrderOffset; +final class SecuredMethodSecurityConfiguration { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor securedAuthorizationMethodInterceptor( ObjectProvider strategyProvider, - ObjectProvider eventPublisherProvider, - ObjectProvider registryProvider, ObjectProvider roleHierarchyProvider, - SecuredMethodSecurityConfiguration configuration) { + ObjectProvider registryProvider) { SecuredAuthorizationManager secured = new SecuredAuthorizationManager(); - AuthoritiesAuthorizationManager authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager(); - RoleHierarchy roleHierarchy = roleHierarchyProvider.getIfAvailable(NullRoleHierarchy::new); - authoritiesAuthorizationManager.setRoleHierarchy(roleHierarchy); - secured.setAuthoritiesAuthorizationManager(authoritiesAuthorizationManager); SecurityContextHolderStrategy strategy = strategyProvider .getIfAvailable(SecurityContextHolder::getContextHolderStrategy); AuthorizationManager manager = new DeferringObservationAuthorizationManager<>( registryProvider, secured); AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor .secured(manager); - interceptor.setOrder(interceptor.getOrder() + configuration.interceptorOrderOffset); interceptor.setSecurityContextHolderStrategy(strategy); - eventPublisherProvider.ifAvailable(interceptor::setAuthorizationEventPublisher); return interceptor; } - @Override - public void setImportMetadata(AnnotationMetadata importMetadata) { - EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize(); - this.interceptorOrderOffset = annotation.offset(); - } - } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java index 24bd18f75a5..ef7826412c0 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java @@ -51,13 +51,11 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; @@ -185,8 +183,7 @@ static final class OAuth2AuthorizedClientManagerRegistrar RefreshTokenOAuth2AuthorizedClientProvider.class, ClientCredentialsOAuth2AuthorizedClientProvider.class, PasswordOAuth2AuthorizedClientProvider.class, - JwtBearerOAuth2AuthorizedClientProvider.class, - TokenExchangeOAuth2AuthorizedClientProvider.class + JwtBearerOAuth2AuthorizedClientProvider.class ); // @formatter:on @@ -258,12 +255,6 @@ OAuth2AuthorizedClientManager getAuthorizedClientManager() { authorizedClientProviders.add(jwtBearerAuthorizedClientProvider); } - OAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = getTokenExchangeAuthorizedClientProvider( - authorizedClientProviderBeans); - if (tokenExchangeAuthorizedClientProvider != null) { - authorizedClientProviders.add(tokenExchangeAuthorizedClientProvider); - } - authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans)); authorizedClientProvider = new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders); } @@ -373,25 +364,6 @@ private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider( return authorizedClientProvider; } - private OAuth2AuthorizedClientProvider getTokenExchangeAuthorizedClientProvider( - Collection authorizedClientProviders) { - TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, TokenExchangeOAuth2AuthorizedClientProvider.class); - - OAuth2AccessTokenResponseClient accessTokenResponseClient = getBeanOfType( - ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class, - TokenExchangeGrantRequest.class)); - if (accessTokenResponseClient != null) { - if (authorizedClientProvider == null) { - authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider(); - } - - authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient); - } - - return authorizedClientProvider; - } - private List getAdditionalAuthorizedClientProviders( Collection authorizedClientProviders) { List additionalAuthorizedClientProviders = new ArrayList<>( diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java index 82b2d329940..1de4750a494 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java @@ -17,7 +17,6 @@ package org.springframework.security.config.annotation.web.configurers; import java.util.List; -import java.util.function.Function; import java.util.function.Supplier; import io.micrometer.observation.ObservationRegistry; @@ -31,14 +30,12 @@ import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.AuthorizationManagers; import org.springframework.security.authorization.ObservationAuthorizationManager; import org.springframework.security.authorization.SpringAuthorizationEventPublisher; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.core.GrantedAuthorityDefaults; -import org.springframework.security.core.Authentication; import org.springframework.security.web.access.intercept.AuthorizationFilter; import org.springframework.security.web.access.intercept.RequestAuthorizationContext; import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager; @@ -247,14 +244,11 @@ public H and() { * {@link RequestMatcher}s. * * @author Evgeniy Cheban - * @author Josh Cummings */ public class AuthorizedUrl { private final List matchers; - private boolean not; - /** * Creates an instance. * @param matchers the {@link RequestMatcher} instances to map @@ -267,16 +261,6 @@ protected List getMatchers() { return this.matchers; } - /** - * Negates the following authorization rule. - * @return the {@link AuthorizedUrl} for further customization - * @since 6.3 - */ - public AuthorizedUrl not() { - this.not = true; - return this; - } - /** * Specify that URLs are allowed by anyone. * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further @@ -389,21 +373,6 @@ public AuthorizationManagerRequestMatcherRegistry anonymous() { return access(AuthenticatedAuthorizationManager.anonymous()); } - /** - * Specify that a path variable in URL to be compared. - * - *

- * For example,

-		 * requestMatchers("/user/{username}").hasVariable("username").equalTo(Authentication::getName)
-		 * 
- * @param variable the variable in URL template to compare. - * @return {@link AuthorizedUrlVariable} for further customization. - * @since 6.3 - */ - public AuthorizedUrlVariable hasVariable(String variable) { - return new AuthorizedUrlVariable(variable); - } - /** * Allows specifying a custom {@link AuthorizationManager}. * @param manager the {@link AuthorizationManager} to use @@ -413,44 +382,7 @@ public AuthorizedUrlVariable hasVariable(String variable) { public AuthorizationManagerRequestMatcherRegistry access( AuthorizationManager manager) { Assert.notNull(manager, "manager cannot be null"); - return (this.not) - ? AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, AuthorizationManagers.not(manager)) - : AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager); - } - - /** - * An object that allows configuring {@link RequestMatcher}s with URI path - * variables - * - * @author Taehong Kim - * @since 6.3 - */ - public final class AuthorizedUrlVariable { - - private final String variable; - - private AuthorizedUrlVariable(String variable) { - this.variable = variable; - } - - /** - * Compares the value of a path variable in the URI with an `Authentication` - * attribute - *

- * For example,

-			 * requestMatchers("/user/{username}").hasVariable("username").equalTo(Authentication::getName));
-			 * 
- * @param function a function to get value from {@link Authentication}. - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customization. - */ - public AuthorizationManagerRequestMatcherRegistry equalTo(Function function) { - return access((auth, requestContext) -> { - String value = requestContext.getVariables().get(this.variable); - return new AuthorizationDecision(function.apply(auth.get()).equals(value)); - }); - } - + return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/DefaultOidcLogoutTokenValidatorFactory.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/DefaultOidcLogoutTokenValidatorFactory.java index bbf32d09c92..fa7fe8e7467 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/DefaultOidcLogoutTokenValidatorFactory.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/DefaultOidcLogoutTokenValidatorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -19,15 +19,17 @@ import java.util.function.Function; import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtValidators; +import org.springframework.security.oauth2.jwt.JwtTimestampValidator; final class DefaultOidcLogoutTokenValidatorFactory implements Function> { @Override public OAuth2TokenValidator apply(ClientRegistration clientRegistration) { - return JwtValidators.createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration)); + return new DelegatingOAuth2TokenValidator<>(new JwtTimestampValidator(), + new OidcBackChannelLogoutTokenValidator(clientRegistration)); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientConfiguration.java deleted file mode 100644 index 07808221f82..00000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientConfiguration.java +++ /dev/null @@ -1,413 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.web.reactive; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; - -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.BeanInitializationException; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; -import org.springframework.context.annotation.AnnotationBeanNameGenerator; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.ResolvableType; -import org.springframework.security.oauth2.client.AuthorizationCodeReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.DelegatingReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.JwtBearerReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.PasswordReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.TokenExchangeReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; -import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver; -import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.web.reactive.config.WebFluxConfigurer; -import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; - -/** - * {@link Configuration} for OAuth 2.0 Client support. - * - *

- * This {@code Configuration} is conditionally imported by - * {@link ReactiveOAuth2ClientImportSelector} when the - * {@code spring-security-oauth2-client} module is present on the classpath. - * - * @author Steve Riesenberg - * @since 6.3 - * @see ReactiveOAuth2ClientImportSelector - */ -@Import({ ReactiveOAuth2ClientConfiguration.ReactiveOAuth2AuthorizedClientManagerConfiguration.class, - ReactiveOAuth2ClientConfiguration.OAuth2ClientWebFluxSecurityConfiguration.class }) -final class ReactiveOAuth2ClientConfiguration { - - @Configuration(proxyBeanMethods = false) - static class ReactiveOAuth2AuthorizedClientManagerConfiguration { - - @Bean(name = ReactiveOAuth2AuthorizedClientManagerRegistrar.BEAN_NAME) - ReactiveOAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar() { - return new ReactiveOAuth2AuthorizedClientManagerRegistrar(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class OAuth2ClientWebFluxSecurityConfiguration implements WebFluxConfigurer { - - private ReactiveOAuth2AuthorizedClientManager authorizedClientManager; - - private ReactiveOAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar; - - @Override - public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { - ReactiveOAuth2AuthorizedClientManager authorizedClientManager = getAuthorizedClientManager(); - if (authorizedClientManager != null) { - configurer.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver(authorizedClientManager)); - } - } - - @Autowired(required = false) - void setAuthorizedClientManager(List authorizedClientManager) { - if (authorizedClientManager.size() == 1) { - this.authorizedClientManager = authorizedClientManager.get(0); - } - } - - @Autowired - void setAuthorizedClientManagerRegistrar( - ReactiveOAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar) { - this.authorizedClientManagerRegistrar = authorizedClientManagerRegistrar; - } - - private ReactiveOAuth2AuthorizedClientManager getAuthorizedClientManager() { - if (this.authorizedClientManager != null) { - return this.authorizedClientManager; - } - return this.authorizedClientManagerRegistrar.getAuthorizedClientManagerIfAvailable(); - } - - } - - /** - * A registrar for registering the default - * {@link ReactiveOAuth2AuthorizedClientManager} bean definition, if not already - * present. - */ - static final class ReactiveOAuth2AuthorizedClientManagerRegistrar - implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware { - - static final String BEAN_NAME = "authorizedClientManagerRegistrar"; - - static final String FACTORY_METHOD_NAME = "getAuthorizedClientManager"; - - // @formatter:off - private static final Set> KNOWN_AUTHORIZED_CLIENT_PROVIDERS = Set.of( - AuthorizationCodeReactiveOAuth2AuthorizedClientProvider.class, - RefreshTokenReactiveOAuth2AuthorizedClientProvider.class, - ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class, - PasswordReactiveOAuth2AuthorizedClientProvider.class, - JwtBearerReactiveOAuth2AuthorizedClientProvider.class, - TokenExchangeReactiveOAuth2AuthorizedClientProvider.class - ); - // @formatter:on - - private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); - - private ListableBeanFactory beanFactory; - - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { - if (getBeanNamesForType(ReactiveOAuth2AuthorizedClientManager.class).length != 0 - || getBeanNamesForType(ReactiveClientRegistrationRepository.class).length != 1 - || getBeanNamesForType(ServerOAuth2AuthorizedClientRepository.class).length != 1 - && getBeanNamesForType(ReactiveOAuth2AuthorizedClientService.class).length != 1) { - return; - } - - BeanDefinition beanDefinition = BeanDefinitionBuilder - .rootBeanDefinition(ReactiveOAuth2AuthorizedClientManager.class) - .setFactoryMethodOnBean(FACTORY_METHOD_NAME, BEAN_NAME) - .getBeanDefinition(); - - registry.registerBeanDefinition(this.beanNameGenerator.generateBeanName(beanDefinition, registry), - beanDefinition); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = (ListableBeanFactory) beanFactory; - } - - ReactiveOAuth2AuthorizedClientManager getAuthorizedClientManagerIfAvailable() { - if (getBeanNamesForType(ReactiveClientRegistrationRepository.class).length != 1 - || getBeanNamesForType(ServerOAuth2AuthorizedClientRepository.class).length != 1 - && getBeanNamesForType(ReactiveOAuth2AuthorizedClientService.class).length != 1) { - return null; - } - return getAuthorizedClientManager(); - } - - ReactiveOAuth2AuthorizedClientManager getAuthorizedClientManager() { - ReactiveClientRegistrationRepository clientRegistrationRepository = BeanFactoryUtils - .beanOfTypeIncludingAncestors(this.beanFactory, ReactiveClientRegistrationRepository.class, true, true); - - ServerOAuth2AuthorizedClientRepository authorizedClientRepository; - try { - authorizedClientRepository = BeanFactoryUtils.beanOfTypeIncludingAncestors(this.beanFactory, - ServerOAuth2AuthorizedClientRepository.class, true, true); - } - catch (NoSuchBeanDefinitionException ex) { - ReactiveOAuth2AuthorizedClientService authorizedClientService = BeanFactoryUtils - .beanOfTypeIncludingAncestors(this.beanFactory, ReactiveOAuth2AuthorizedClientService.class, true, - true); - authorizedClientRepository = new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository( - authorizedClientService); - } - - Collection authorizedClientProviderBeans = BeanFactoryUtils - .beansOfTypeIncludingAncestors(this.beanFactory, ReactiveOAuth2AuthorizedClientProvider.class, true, - true) - .values(); - - ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider; - if (hasDelegatingAuthorizedClientProvider(authorizedClientProviderBeans)) { - authorizedClientProvider = authorizedClientProviderBeans.iterator().next(); - } - else { - List authorizedClientProviders = new ArrayList<>(); - authorizedClientProviders - .add(getAuthorizationCodeAuthorizedClientProvider(authorizedClientProviderBeans)); - authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider(authorizedClientProviderBeans)); - authorizedClientProviders - .add(getClientCredentialsAuthorizedClientProvider(authorizedClientProviderBeans)); - authorizedClientProviders.add(getPasswordAuthorizedClientProvider(authorizedClientProviderBeans)); - - ReactiveOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = getJwtBearerAuthorizedClientProvider( - authorizedClientProviderBeans); - if (jwtBearerAuthorizedClientProvider != null) { - authorizedClientProviders.add(jwtBearerAuthorizedClientProvider); - } - - ReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = getTokenExchangeAuthorizedClientProvider( - authorizedClientProviderBeans); - if (tokenExchangeAuthorizedClientProvider != null) { - authorizedClientProviders.add(tokenExchangeAuthorizedClientProvider); - } - - authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans)); - authorizedClientProvider = new DelegatingReactiveOAuth2AuthorizedClientProvider( - authorizedClientProviders); - } - - DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); - - Consumer authorizedClientManagerConsumer = getBeanOfType( - ResolvableType.forClassWithGenerics(Consumer.class, - DefaultReactiveOAuth2AuthorizedClientManager.class)); - if (authorizedClientManagerConsumer != null) { - authorizedClientManagerConsumer.accept(authorizedClientManager); - } - - return authorizedClientManager; - } - - private boolean hasDelegatingAuthorizedClientProvider( - Collection authorizedClientProviders) { - if (authorizedClientProviders.size() != 1) { - return false; - } - return authorizedClientProviders.iterator() - .next() instanceof DelegatingReactiveOAuth2AuthorizedClientProvider; - } - - private ReactiveOAuth2AuthorizedClientProvider getAuthorizationCodeAuthorizedClientProvider( - Collection authorizedClientProviders) { - AuthorizationCodeReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, AuthorizationCodeReactiveOAuth2AuthorizedClientProvider.class); - if (authorizedClientProvider == null) { - authorizedClientProvider = new AuthorizationCodeReactiveOAuth2AuthorizedClientProvider(); - } - - return authorizedClientProvider; - } - - private ReactiveOAuth2AuthorizedClientProvider getRefreshTokenAuthorizedClientProvider( - Collection authorizedClientProviders) { - RefreshTokenReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, RefreshTokenReactiveOAuth2AuthorizedClientProvider.class); - if (authorizedClientProvider == null) { - authorizedClientProvider = new RefreshTokenReactiveOAuth2AuthorizedClientProvider(); - } - - ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = getBeanOfType( - ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class, - OAuth2RefreshTokenGrantRequest.class)); - if (accessTokenResponseClient != null) { - authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient); - } - - return authorizedClientProvider; - } - - private ReactiveOAuth2AuthorizedClientProvider getClientCredentialsAuthorizedClientProvider( - Collection authorizedClientProviders) { - ClientCredentialsReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class); - if (authorizedClientProvider == null) { - authorizedClientProvider = new ClientCredentialsReactiveOAuth2AuthorizedClientProvider(); - } - - ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = getBeanOfType( - ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class, - OAuth2ClientCredentialsGrantRequest.class)); - if (accessTokenResponseClient != null) { - authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient); - } - - return authorizedClientProvider; - } - - private ReactiveOAuth2AuthorizedClientProvider getPasswordAuthorizedClientProvider( - Collection authorizedClientProviders) { - PasswordReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, PasswordReactiveOAuth2AuthorizedClientProvider.class); - if (authorizedClientProvider == null) { - authorizedClientProvider = new PasswordReactiveOAuth2AuthorizedClientProvider(); - } - - ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = getBeanOfType( - ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class, - OAuth2PasswordGrantRequest.class)); - if (accessTokenResponseClient != null) { - authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient); - } - - return authorizedClientProvider; - } - - private ReactiveOAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider( - Collection authorizedClientProviders) { - JwtBearerReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, JwtBearerReactiveOAuth2AuthorizedClientProvider.class); - - ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = getBeanOfType( - ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class, - JwtBearerGrantRequest.class)); - if (accessTokenResponseClient != null) { - if (authorizedClientProvider == null) { - authorizedClientProvider = new JwtBearerReactiveOAuth2AuthorizedClientProvider(); - } - - authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient); - } - - return authorizedClientProvider; - } - - private ReactiveOAuth2AuthorizedClientProvider getTokenExchangeAuthorizedClientProvider( - Collection authorizedClientProviders) { - TokenExchangeReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, TokenExchangeReactiveOAuth2AuthorizedClientProvider.class); - - ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = getBeanOfType( - ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class, - TokenExchangeGrantRequest.class)); - if (accessTokenResponseClient != null) { - if (authorizedClientProvider == null) { - authorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider(); - } - - authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient); - } - - return authorizedClientProvider; - } - - private List getAdditionalAuthorizedClientProviders( - Collection authorizedClientProviders) { - List additionalAuthorizedClientProviders = new ArrayList<>( - authorizedClientProviders); - additionalAuthorizedClientProviders - .removeIf((provider) -> KNOWN_AUTHORIZED_CLIENT_PROVIDERS.contains(provider.getClass())); - return additionalAuthorizedClientProviders; - } - - private T getAuthorizedClientProviderByType( - Collection authorizedClientProviders, Class providerClass) { - T authorizedClientProvider = null; - for (ReactiveOAuth2AuthorizedClientProvider current : authorizedClientProviders) { - if (providerClass.isInstance(current)) { - assertAuthorizedClientProviderIsNull(authorizedClientProvider); - authorizedClientProvider = providerClass.cast(current); - } - } - return authorizedClientProvider; - } - - private static void assertAuthorizedClientProviderIsNull( - ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider) { - if (authorizedClientProvider != null) { - // @formatter:off - throw new BeanInitializationException(String.format( - "Unable to create a %s bean. Expected one bean of type %s, but found multiple. " + - "Please consider defining only a single bean of this type, or define a %s bean yourself.", - ReactiveOAuth2AuthorizedClientManager.class.getName(), - authorizedClientProvider.getClass().getName(), - ReactiveOAuth2AuthorizedClientManager.class.getName())); - // @formatter:on - } - } - - private String[] getBeanNamesForType(Class beanClass) { - return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, beanClass, true, true); - } - - private T getBeanOfType(ResolvableType resolvableType) { - ObjectProvider objectProvider = this.beanFactory.getBeanProvider(resolvableType, true); - return objectProvider.getIfAvailable(); - } - - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java index 5e73fb7c894..9a1781b93bf 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,13 +16,27 @@ package org.springframework.security.config.annotation.web.reactive; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver; +import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.util.ClassUtils; +import org.springframework.web.reactive.config.WebFluxConfigurer; +import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; /** - * Used by {@link EnableWebFluxSecurity} to conditionally import - * {@link ReactiveOAuth2ClientConfiguration}. + * {@link Configuration} for OAuth 2.0 Client support. * *

* This {@code Configuration} is imported by {@link EnableWebFluxSecurity} @@ -46,8 +60,85 @@ public String[] selectImports(AnnotationMetadata importingClassMetadata) { if (!oauth2ClientPresent) { return new String[0]; } - return new String[] { - "org.springframework.security.config.annotation.web.reactive.ReactiveOAuth2ClientConfiguration" }; + return new String[] { "org.springframework.security.config.annotation.web.reactive." + + "ReactiveOAuth2ClientImportSelector$OAuth2ClientWebFluxSecurityConfiguration" }; + } + + @Configuration(proxyBeanMethods = false) + static class OAuth2ClientWebFluxSecurityConfiguration implements WebFluxConfigurer { + + private ReactiveClientRegistrationRepository clientRegistrationRepository; + + private ServerOAuth2AuthorizedClientRepository authorizedClientRepository; + + private ReactiveOAuth2AuthorizedClientService authorizedClientService; + + private ReactiveOAuth2AuthorizedClientManager authorizedClientManager; + + @Override + public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { + ReactiveOAuth2AuthorizedClientManager authorizedClientManager = getAuthorizedClientManager(); + if (authorizedClientManager != null) { + configurer.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver(authorizedClientManager)); + } + } + + @Autowired(required = false) + void setClientRegistrationRepository(ReactiveClientRegistrationRepository clientRegistrationRepository) { + this.clientRegistrationRepository = clientRegistrationRepository; + } + + @Autowired(required = false) + void setAuthorizedClientRepository(ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { + this.authorizedClientRepository = authorizedClientRepository; + } + + @Autowired(required = false) + void setAuthorizedClientService(List authorizedClientService) { + if (authorizedClientService.size() == 1) { + this.authorizedClientService = authorizedClientService.get(0); + } + } + + @Autowired(required = false) + void setAuthorizedClientManager(List authorizedClientManager) { + if (authorizedClientManager.size() == 1) { + this.authorizedClientManager = authorizedClientManager.get(0); + } + } + + private ServerOAuth2AuthorizedClientRepository getAuthorizedClientRepository() { + if (this.authorizedClientRepository != null) { + return this.authorizedClientRepository; + } + if (this.authorizedClientService != null) { + return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(this.authorizedClientService); + } + return null; + } + + private ReactiveOAuth2AuthorizedClientManager getAuthorizedClientManager() { + if (this.authorizedClientManager != null) { + return this.authorizedClientManager; + } + ReactiveOAuth2AuthorizedClientManager authorizedClientManager = null; + if (this.authorizedClientRepository != null && this.clientRegistrationRepository != null) { + ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder + .builder() + .authorizationCode() + .refreshToken() + .clientCredentials() + .password() + .build(); + DefaultReactiveOAuth2AuthorizedClientManager defaultReactiveOAuth2AuthorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( + this.clientRegistrationRepository, getAuthorizedClientRepository()); + defaultReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + authorizedClientManager = defaultReactiveOAuth2AuthorizedClientManager; + } + + return authorizedClientManager; + } + } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java index 69c6e75c512..74b8337a4b7 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java @@ -32,7 +32,6 @@ import org.springframework.security.authentication.ObservationReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; -import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; @@ -64,8 +63,6 @@ class ServerHttpSecurityConfiguration { private ReactiveUserDetailsPasswordService userDetailsPasswordService; - private ReactiveCompromisedPasswordChecker compromisedPasswordChecker; - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; @Autowired(required = false) @@ -101,11 +98,6 @@ void setObservationRegistry(ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; } - @Autowired(required = false) - void setCompromisedPasswordChecker(ReactiveCompromisedPasswordChecker compromisedPasswordChecker) { - this.compromisedPasswordChecker = compromisedPasswordChecker; - } - @Bean static WebFluxConfigurer authenticationPrincipalArgumentResolverConfigurer( ObjectProvider authenticationPrincipalArgumentResolver) { @@ -161,7 +153,6 @@ private ReactiveAuthenticationManager authenticationManager() { manager.setPasswordEncoder(this.passwordEncoder); } manager.setUserDetailsPasswordService(this.userDetailsPasswordService); - manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker); if (!this.observationRegistry.isNoop()) { return new ObservationReactiveAuthenticationManager(this.observationRegistry, manager); } diff --git a/config/src/main/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrar.java b/config/src/main/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrar.java index 669d6f7f67f..0020017bb5c 100644 --- a/config/src/main/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrar.java +++ b/config/src/main/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrar.java @@ -44,13 +44,11 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; @@ -78,8 +76,7 @@ final class OAuth2AuthorizedClientManagerRegistrar implements BeanDefinitionRegi RefreshTokenOAuth2AuthorizedClientProvider.class, ClientCredentialsOAuth2AuthorizedClientProvider.class, PasswordOAuth2AuthorizedClientProvider.class, - JwtBearerOAuth2AuthorizedClientProvider.class, - TokenExchangeOAuth2AuthorizedClientProvider.class + JwtBearerOAuth2AuthorizedClientProvider.class ); // @formatter:on @@ -140,12 +137,6 @@ private OAuth2AuthorizedClientManager getAuthorizedClientManager() { authorizedClientProviders.add(jwtBearerAuthorizedClientProvider); } - OAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = getTokenExchangeAuthorizedClientProvider( - authorizedClientProviderBeans); - if (tokenExchangeAuthorizedClientProvider != null) { - authorizedClientProviders.add(tokenExchangeAuthorizedClientProvider); - } - authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans)); authorizedClientProvider = new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders); } @@ -254,25 +245,6 @@ private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider( return authorizedClientProvider; } - private OAuth2AuthorizedClientProvider getTokenExchangeAuthorizedClientProvider( - Collection authorizedClientProviders) { - TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, TokenExchangeOAuth2AuthorizedClientProvider.class); - - OAuth2AccessTokenResponseClient accessTokenResponseClient = getBeanOfType( - ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class, - TokenExchangeGrantRequest.class)); - if (accessTokenResponseClient != null) { - if (authorizedClientProvider == null) { - authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider(); - } - - authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient); - } - - return authorizedClientProvider; - } - private List getAdditionalAuthorizedClientProviders( Collection authorizedClientProviders) { List additionalAuthorizedClientProviders = new ArrayList<>( diff --git a/config/src/main/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParser.java index 60fe23c43ec..5a06e2d3145 100644 --- a/config/src/main/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParser.java @@ -64,8 +64,6 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean private static final String ELT_ENCRYPTION_CREDENTIAL = "encryption-credential"; - private static final String ATT_ID = "id"; - private static final String ATT_REGISTRATION_ID = "registration-id"; private static final String ATT_ASSERTING_PARTY_ID = "asserting-party-id"; @@ -110,11 +108,8 @@ public BeanDefinition parse(Element element, ParserContext parserContext) { .rootBeanDefinition(InMemoryRelyingPartyRegistrationRepository.class) .addConstructorArgValue(relyingPartyRegistrations) .getBeanDefinition(); - String relyingPartyRegistrationRepositoryId = element.getAttribute(ATT_ID); - if (!StringUtils.hasText(relyingPartyRegistrationRepositoryId)) { - relyingPartyRegistrationRepositoryId = parserContext.getReaderContext() - .generateBeanName(relyingPartyRegistrationRepositoryBean); - } + String relyingPartyRegistrationRepositoryId = parserContext.getReaderContext() + .generateBeanName(relyingPartyRegistrationRepositoryBean); parserContext.registerBeanComponent(new BeanComponentDefinition(relyingPartyRegistrationRepositoryBean, relyingPartyRegistrationRepositoryId)); parserContext.popAndRegisterContainingComponent(); diff --git a/config/src/main/java/org/springframework/security/config/web/server/DefaultOidcLogoutTokenValidatorFactory.java b/config/src/main/java/org/springframework/security/config/web/server/DefaultOidcLogoutTokenValidatorFactory.java index 23d8502c800..22c26aa8d75 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/DefaultOidcLogoutTokenValidatorFactory.java +++ b/config/src/main/java/org/springframework/security/config/web/server/DefaultOidcLogoutTokenValidatorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -19,15 +19,17 @@ import java.util.function.Function; import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtValidators; +import org.springframework.security.oauth2.jwt.JwtTimestampValidator; final class DefaultOidcLogoutTokenValidatorFactory implements Function> { @Override public OAuth2TokenValidator apply(ClientRegistration clientRegistration) { - return JwtValidators.createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration)); + return new DelegatingOAuth2TokenValidator<>(new JwtTimestampValidator(), + new OidcBackChannelLogoutTokenValidator(clientRegistration)); } } diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 3820b29d140..16512b238f0 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -63,7 +63,6 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; -import org.springframework.security.core.session.ReactiveSessionRegistry; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; @@ -127,26 +126,19 @@ import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter; import org.springframework.security.web.server.authentication.AuthenticationConverterServerWebExchangeMatcher; import org.springframework.security.web.server.authentication.AuthenticationWebFilter; -import org.springframework.security.web.server.authentication.ConcurrentSessionControlServerAuthenticationSuccessHandler; -import org.springframework.security.web.server.authentication.DelegatingServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint; import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; -import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler; import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; -import org.springframework.security.web.server.authentication.RegisterSessionServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPointFailureHandler; import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter; import org.springframework.security.web.server.authentication.ServerHttpBasicAuthenticationConverter; -import org.springframework.security.web.server.authentication.ServerMaximumSessionsExceededHandler; import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter; -import org.springframework.security.web.server.authentication.SessionLimit; -import org.springframework.security.web.server.authentication.WebFilterChainServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler; import org.springframework.security.web.server.authentication.logout.LogoutWebFilter; import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler; @@ -216,8 +208,6 @@ import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebSession; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; -import org.springframework.web.server.session.DefaultWebSessionManager; import org.springframework.web.util.pattern.PathPatternParser; /** @@ -324,8 +314,6 @@ public class ServerHttpSecurity { private LoginPageSpec loginPage = new LoginPageSpec(); - private SessionManagementSpec sessionManagement; - private ReactiveAuthenticationManager authenticationManager; private ServerSecurityContextRepository securityContextRepository; @@ -374,7 +362,6 @@ public ServerHttpSecurity addFilterAt(WebFilter webFilter, SecurityWebFiltersOrd } /** - * * Adds a {@link WebFilter} before specific position. * @param webFilter the {@link WebFilter} to add * @param order the place before which to insert the {@link WebFilter} @@ -758,36 +745,6 @@ public ServerHttpSecurity httpBasic(Customizer httpBasicCustomize return this; } - /** - * Configures Session Management. An example configuration is provided below: - *

-	 *  @Bean
-	 *  SecurityWebFilterChain filterChain(ServerHttpSecurity http, ReactiveSessionRegistry sessionRegistry) {
-	 *      http
-	 *          // ...
-	 *          .sessionManagement((sessionManagement) -> sessionManagement
-	 *              .concurrentSessions((concurrentSessions) -> concurrentSessions
-	 *                  .maxSessions(1)
-	 *                  .maxSessionsPreventsLogin(true)
-	 *                  .sessionRegistry(sessionRegistry)
-	 *              )
-	 *          );
-	 *      return http.build();
-	 *  }
-	 * 
- * @param customizer the {@link Customizer} to provide more options for the - * {@link SessionManagementSpec} - * @return the {@link ServerHttpSecurity} to continue configuring - * @since 6.3 - */ - public ServerHttpSecurity sessionManagement(Customizer customizer) { - if (this.sessionManagement == null) { - this.sessionManagement = new SessionManagementSpec(); - } - customizer.customize(this.sessionManagement); - return this; - } - /** * Configures password management. An example configuration is provided below: * @@ -1562,9 +1519,6 @@ public SecurityWebFilterChain build() { } WebFilter securityContextRepositoryWebFilter = securityContextRepositoryWebFilter(); this.webFilters.add(securityContextRepositoryWebFilter); - if (this.sessionManagement != null) { - this.sessionManagement.configure(this); - } if (this.httpsRedirectSpec != null) { this.httpsRedirectSpec.configure(this); } @@ -1955,270 +1909,6 @@ public AuthorizeExchangeSpec access(ReactiveAuthorizationManager customizer) { - if (this.concurrentSessions == null) { - this.concurrentSessions = new ConcurrentSessionsSpec(); - } - customizer.customize(this.concurrentSessions); - return this; - } - - void configure(ServerHttpSecurity http) { - if (this.concurrentSessions != null) { - ReactiveSessionRegistry reactiveSessionRegistry = getSessionRegistry(); - ConcurrentSessionControlServerAuthenticationSuccessHandler concurrentSessionControlStrategy = new ConcurrentSessionControlServerAuthenticationSuccessHandler( - reactiveSessionRegistry, getMaximumSessionsExceededHandler()); - concurrentSessionControlStrategy.setSessionLimit(this.sessionLimit); - RegisterSessionServerAuthenticationSuccessHandler registerSessionAuthenticationStrategy = new RegisterSessionServerAuthenticationSuccessHandler( - reactiveSessionRegistry); - this.authenticationSuccessHandler = new DelegatingServerAuthenticationSuccessHandler( - concurrentSessionControlStrategy, registerSessionAuthenticationStrategy); - SessionRegistryWebFilter sessionRegistryWebFilter = new SessionRegistryWebFilter( - reactiveSessionRegistry); - configureSuccessHandlerOnAuthenticationFilters(); - http.addFilterAfter(sessionRegistryWebFilter, SecurityWebFiltersOrder.HTTP_HEADERS_WRITER); - } - } - - private ServerMaximumSessionsExceededHandler getMaximumSessionsExceededHandler() { - if (this.maximumSessionsExceededHandler != null) { - return this.maximumSessionsExceededHandler; - } - DefaultWebSessionManager webSessionManager = getBeanOrNull( - WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME, DefaultWebSessionManager.class); - if (webSessionManager != null) { - this.maximumSessionsExceededHandler = new InvalidateLeastUsedServerMaximumSessionsExceededHandler( - webSessionManager.getSessionStore()); - } - if (this.maximumSessionsExceededHandler == null) { - throw new IllegalStateException( - "Could not create a default ServerMaximumSessionsExceededHandler. Please provide " - + "a ServerMaximumSessionsExceededHandler via DSL"); - } - return this.maximumSessionsExceededHandler; - } - - private void configureSuccessHandlerOnAuthenticationFilters() { - if (ServerHttpSecurity.this.formLogin != null) { - ServerHttpSecurity.this.formLogin.defaultSuccessHandlers.add(0, this.authenticationSuccessHandler); - } - if (ServerHttpSecurity.this.oauth2Login != null) { - ServerHttpSecurity.this.oauth2Login.defaultSuccessHandlers.add(0, this.authenticationSuccessHandler); - } - if (ServerHttpSecurity.this.httpBasic != null) { - ServerHttpSecurity.this.httpBasic.defaultSuccessHandlers.add(0, this.authenticationSuccessHandler); - } - } - - private ReactiveSessionRegistry getSessionRegistry() { - if (this.sessionRegistry == null) { - this.sessionRegistry = getBeanOrNull(ReactiveSessionRegistry.class); - } - if (this.sessionRegistry == null) { - throw new IllegalStateException( - "A ReactiveSessionRegistry is needed for concurrent session management"); - } - return this.sessionRegistry; - } - - /** - * Configures how many sessions are allowed for a given user. - */ - public class ConcurrentSessionsSpec { - - /** - * Sets the {@link ReactiveSessionRegistry} to use. - * @param reactiveSessionRegistry the {@link ReactiveSessionRegistry} to use - * @return the {@link ConcurrentSessionsSpec} to continue customizing - */ - public ConcurrentSessionsSpec sessionRegistry(ReactiveSessionRegistry reactiveSessionRegistry) { - SessionManagementSpec.this.sessionRegistry = reactiveSessionRegistry; - return this; - } - - /** - * Sets the maximum number of sessions allowed for any user. You can use - * {@link SessionLimit#of(int)} to specify a positive integer or - * {@link SessionLimit#UNLIMITED} to allow unlimited sessions. To customize - * the maximum number of sessions on a per-user basis, you can provide a - * custom {@link SessionLimit} implementation, like so:
-			 *     http
-			 *         .sessionManagement((sessions) -> sessions
-			 *             .concurrentSessions((concurrency) -> concurrency
-			 *                 .maximumSessions((authentication) -> {
-			 *                     if (authentication.getName().equals("admin")) {
-			 *                         return Mono.empty() // unlimited sessions for admin
-			 *                     }
-			 *                     return Mono.just(1); // one session for every other user
-			 *                 })
-			 *             )
-			 *         )
-			 * 
- * @param sessionLimit the maximum number of sessions allowed for any user - * @return the {@link ConcurrentSessionsSpec} to continue customizing - */ - public ConcurrentSessionsSpec maximumSessions(SessionLimit sessionLimit) { - Assert.notNull(sessionLimit, "sessionLimit cannot be null"); - SessionManagementSpec.this.sessionLimit = sessionLimit; - return this; - } - - /** - * Sets the {@link ServerMaximumSessionsExceededHandler} to use when the - * maximum number of sessions is exceeded. - * @param maximumSessionsExceededHandler the - * {@link ServerMaximumSessionsExceededHandler} to use - * @return the {@link ConcurrentSessionsSpec} to continue customizing - */ - public ConcurrentSessionsSpec maximumSessionsExceededHandler( - ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler) { - Assert.notNull(maximumSessionsExceededHandler, "maximumSessionsExceededHandler cannot be null"); - SessionManagementSpec.this.maximumSessionsExceededHandler = maximumSessionsExceededHandler; - return this; - } - - } - - private static final class SessionRegistryWebFilter implements WebFilter { - - private final ReactiveSessionRegistry sessionRegistry; - - private SessionRegistryWebFilter(ReactiveSessionRegistry sessionRegistry) { - Assert.notNull(sessionRegistry, "sessionRegistry cannot be null"); - this.sessionRegistry = sessionRegistry; - } - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - return chain.filter(new SessionRegistryWebExchange(exchange)); - } - - private final class SessionRegistryWebExchange extends ServerWebExchangeDecorator { - - private final Mono sessionMono; - - private SessionRegistryWebExchange(ServerWebExchange delegate) { - super(delegate); - this.sessionMono = delegate.getSession() - .flatMap((session) -> SessionRegistryWebFilter.this.sessionRegistry - .updateLastAccessTime(session.getId()) - .thenReturn(session)) - .map(SessionRegistryWebSession::new); - } - - @Override - public Mono getSession() { - return this.sessionMono; - } - - } - - private final class SessionRegistryWebSession implements WebSession { - - private final WebSession session; - - private SessionRegistryWebSession(WebSession session) { - this.session = session; - } - - @Override - public String getId() { - return this.session.getId(); - } - - @Override - public Map getAttributes() { - return this.session.getAttributes(); - } - - @Override - public void start() { - this.session.start(); - } - - @Override - public boolean isStarted() { - return this.session.isStarted(); - } - - @Override - public Mono changeSessionId() { - String currentId = this.session.getId(); - return this.session.changeSessionId() - .then(Mono.defer( - () -> SessionRegistryWebFilter.this.sessionRegistry.removeSessionInformation(currentId) - .flatMap((information) -> { - information = information.withSessionId(this.session.getId()); - return SessionRegistryWebFilter.this.sessionRegistry - .saveSessionInformation(information); - }))); - } - - @Override - public Mono invalidate() { - String currentId = this.session.getId(); - return this.session.invalidate() - .then(Mono.defer(() -> SessionRegistryWebFilter.this.sessionRegistry - .removeSessionInformation(currentId))) - .then(); - } - - @Override - public Mono save() { - return this.session.save(); - } - - @Override - public boolean isExpired() { - return this.session.isExpired(); - } - - @Override - public Instant getCreationTime() { - return this.session.getCreationTime(); - } - - @Override - public Instant getLastAccessTime() { - return this.session.getLastAccessTime(); - } - - @Override - public void setMaxIdleTime(Duration maxIdleTime) { - this.session.setMaxIdleTime(maxIdleTime); - } - - @Override - public Duration getMaxIdleTime() { - return this.session.getMaxIdleTime(); - } - - } - - } - - } - /** * Configures HTTPS redirection rules * @@ -2523,11 +2213,6 @@ public final class HttpBasicSpec { private ServerAuthenticationFailureHandler authenticationFailureHandler; - private final List defaultSuccessHandlers = new ArrayList<>( - List.of(new WebFilterChainServerAuthenticationSuccessHandler())); - - private List authenticationSuccessHandlers = new ArrayList<>(); - private HttpBasicSpec() { List entryPoints = new ArrayList<>(); entryPoints @@ -2538,40 +2223,6 @@ private HttpBasicSpec() { this.entryPoint = defaultEntryPoint; } - /** - * The {@link ServerAuthenticationSuccessHandler} used after authentication - * success. Defaults to {@link WebFilterChainServerAuthenticationSuccessHandler}. - * Note that this method clears previously added success handlers via - * {@link #authenticationSuccessHandler(Consumer)} - * @param authenticationSuccessHandler the success handler to use - * @return the {@link HttpBasicSpec} to continue configuring - * @since 6.3 - */ - public HttpBasicSpec authenticationSuccessHandler( - ServerAuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - authenticationSuccessHandler((handlers) -> { - handlers.clear(); - handlers.add(authenticationSuccessHandler); - }); - return this; - } - - /** - * Allows customizing the list of {@link ServerAuthenticationSuccessHandler}. The - * default list contains a - * {@link WebFilterChainServerAuthenticationSuccessHandler}. - * @param handlersConsumer the handlers consumer - * @return the {@link HttpBasicSpec} to continue configuring - * @since 6.3 - */ - public HttpBasicSpec authenticationSuccessHandler( - Consumer> handlersConsumer) { - Assert.notNull(handlersConsumer, "handlersConsumer cannot be null"); - handlersConsumer.accept(this.authenticationSuccessHandlers); - return this; - } - /** * The {@link ReactiveAuthenticationManager} used to authenticate. Defaults to * {@link ServerHttpSecurity#authenticationManager(ReactiveAuthenticationManager)}. @@ -2657,17 +2308,9 @@ protected void configure(ServerHttpSecurity http) { authenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler()); authenticationFilter.setAuthenticationConverter(new ServerHttpBasicAuthenticationConverter()); authenticationFilter.setSecurityContextRepository(this.securityContextRepository); - authenticationFilter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler(http)); http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC); } - private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) { - if (this.authenticationSuccessHandlers.isEmpty()) { - return new DelegatingServerAuthenticationSuccessHandler(this.defaultSuccessHandlers); - } - return new DelegatingServerAuthenticationSuccessHandler(this.authenticationSuccessHandlers); - } - private ServerAuthenticationFailureHandler authenticationFailureHandler() { if (this.authenticationFailureHandler != null) { return this.authenticationFailureHandler; @@ -2739,9 +2382,6 @@ public final class FormLoginSpec { private final RedirectServerAuthenticationSuccessHandler defaultSuccessHandler = new RedirectServerAuthenticationSuccessHandler( "/"); - private final List defaultSuccessHandlers = new ArrayList<>( - List.of(this.defaultSuccessHandler)); - private RedirectServerAuthenticationEntryPoint defaultEntryPoint; private ReactiveAuthenticationManager authenticationManager; @@ -2756,7 +2396,7 @@ public final class FormLoginSpec { private ServerAuthenticationFailureHandler authenticationFailureHandler; - private List authenticationSuccessHandlers = new ArrayList<>(); + private ServerAuthenticationSuccessHandler authenticationSuccessHandler = this.defaultSuccessHandler; private FormLoginSpec() { } @@ -2774,34 +2414,14 @@ public FormLoginSpec authenticationManager(ReactiveAuthenticationManager authent /** * The {@link ServerAuthenticationSuccessHandler} used after authentication - * success. Defaults to {@link RedirectServerAuthenticationSuccessHandler}. Note - * that this method clears previously added success handlers via - * {@link #authenticationSuccessHandler(Consumer)} + * success. Defaults to {@link RedirectServerAuthenticationSuccessHandler}. * @param authenticationSuccessHandler the success handler to use * @return the {@link FormLoginSpec} to continue configuring */ public FormLoginSpec authenticationSuccessHandler( ServerAuthenticationSuccessHandler authenticationSuccessHandler) { Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - authenticationSuccessHandler((handlers) -> { - handlers.clear(); - handlers.add(authenticationSuccessHandler); - }); - return this; - } - - /** - * Allows customizing the list of {@link ServerAuthenticationSuccessHandler}. The - * default list contains a {@link RedirectServerAuthenticationSuccessHandler} that - * redirects to "/". - * @param handlersConsumer the handlers consumer - * @return the {@link FormLoginSpec} to continue configuring - * @since 6.3 - */ - public FormLoginSpec authenticationSuccessHandler( - Consumer> handlersConsumer) { - Assert.notNull(handlersConsumer, "handlersConsumer cannot be null"); - handlersConsumer.accept(this.authenticationSuccessHandlers); + this.authenticationSuccessHandler = authenticationSuccessHandler; return this; } @@ -2934,18 +2554,11 @@ protected void configure(ServerHttpSecurity http) { authenticationFilter.setRequiresAuthenticationMatcher(this.requiresAuthenticationMatcher); authenticationFilter.setAuthenticationFailureHandler(this.authenticationFailureHandler); authenticationFilter.setAuthenticationConverter(new ServerFormLoginAuthenticationConverter()); - authenticationFilter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler(http)); + authenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler); authenticationFilter.setSecurityContextRepository(this.securityContextRepository); http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.FORM_LOGIN); } - private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) { - if (this.authenticationSuccessHandlers.isEmpty()) { - return new DelegatingServerAuthenticationSuccessHandler(this.defaultSuccessHandlers); - } - return new DelegatingServerAuthenticationSuccessHandler(this.authenticationSuccessHandlers); - } - } private final class LoginPageSpec { @@ -4124,12 +3737,7 @@ public final class OAuth2LoginSpec { private ReactiveOidcSessionRegistry oidcSessionRegistry; - private final RedirectServerAuthenticationSuccessHandler defaultAuthenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler(); - - private final List defaultSuccessHandlers = new ArrayList<>( - List.of(this.defaultAuthenticationSuccessHandler)); - - private List authenticationSuccessHandlers = new ArrayList<>(); + private ServerAuthenticationSuccessHandler authenticationSuccessHandler; private ServerAuthenticationFailureHandler authenticationFailureHandler; @@ -4177,8 +3785,7 @@ public OAuth2LoginSpec oidcSessionRegistry(ReactiveOidcSessionRegistry oidcSessi /** * The {@link ServerAuthenticationSuccessHandler} used after authentication * success. Defaults to {@link RedirectServerAuthenticationSuccessHandler} - * redirecting to "/". Note that this method clears previously added success - * handlers via {@link #authenticationSuccessHandler(Consumer)} + * redirecting to "/". * @param authenticationSuccessHandler the success handler to use * @return the {@link OAuth2LoginSpec} to customize * @since 5.2 @@ -4186,25 +3793,7 @@ public OAuth2LoginSpec oidcSessionRegistry(ReactiveOidcSessionRegistry oidcSessi public OAuth2LoginSpec authenticationSuccessHandler( ServerAuthenticationSuccessHandler authenticationSuccessHandler) { Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - authenticationSuccessHandler((handlers) -> { - handlers.clear(); - handlers.add(authenticationSuccessHandler); - }); - return this; - } - - /** - * Allows customizing the list of {@link ServerAuthenticationSuccessHandler}. The - * default list contains a {@link RedirectServerAuthenticationSuccessHandler} that - * redirects to "/". - * @param handlersConsumer the handlers consumer - * @return the {@link OAuth2LoginSpec} to continue configuring - * @since 6.3 - */ - public OAuth2LoginSpec authenticationSuccessHandler( - Consumer> handlersConsumer) { - Assert.notNull(handlersConsumer, "handlersConsumer cannot be null"); - handlersConsumer.accept(this.authenticationSuccessHandlers); + this.authenticationSuccessHandler = authenticationSuccessHandler; return this; } @@ -4461,11 +4050,12 @@ private ReactiveOidcSessionRegistry getOidcSessionRegistry() { } private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) { - this.defaultAuthenticationSuccessHandler.setRequestCache(http.requestCache.requestCache); - if (this.authenticationSuccessHandlers.isEmpty()) { - return new DelegatingServerAuthenticationSuccessHandler(this.defaultSuccessHandlers); + if (this.authenticationSuccessHandler == null) { + RedirectServerAuthenticationSuccessHandler handler = new RedirectServerAuthenticationSuccessHandler(); + handler.setRequestCache(http.requestCache.requestCache); + this.authenticationSuccessHandler = handler; } - return new DelegatingServerAuthenticationSuccessHandler(this.authenticationSuccessHandlers); + return this.authenticationSuccessHandler; } private ServerAuthenticationFailureHandler getAuthenticationFailureHandler() { diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt index 8bb9fedf109..a61165f33cc 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -26,7 +26,6 @@ import org.springframework.security.config.annotation.web.configurers.AuthorizeH import org.springframework.security.core.Authentication import org.springframework.security.web.access.intercept.AuthorizationFilter import org.springframework.security.web.access.intercept.RequestAuthorizationContext -import org.springframework.security.web.access.IpAddressAuthorizationManager import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher import org.springframework.security.web.util.matcher.AnyRequestMatcher import org.springframework.security.web.util.matcher.RequestMatcher @@ -223,13 +222,6 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() { return AuthorityAuthorizationManager.hasAnyRole(*roles) } - /** - * Require a specific IP or range of IP addresses. - * @since 6.3 - */ - fun hasIpAddress(ipAddress: String): AuthorizationManager = - IpAddressAuthorizationManager.hasIpAddress(ipAddress) - /** * Specify that URLs are allowed by anyone. */ diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/FormLoginDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/FormLoginDsl.kt index 0da773f570a..3a03ddf170a 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/FormLoginDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/FormLoginDsl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -38,8 +38,6 @@ import jakarta.servlet.http.HttpServletRequest * @property loginProcessingUrl the URL to validate the credentials * @property permitAll whether to grant access to the urls for [failureUrl] as well as * for the [HttpSecurityBuilder], the [loginPage] and [loginProcessingUrl] for every user - * @property usernameParameter the HTTP parameter to look for the username when performing authentication - * @property passwordParameter the HTTP parameter to look for the password when performing authentication */ @SecurityMarker class FormLoginDsl { @@ -50,8 +48,6 @@ class FormLoginDsl { var loginProcessingUrl: String? = null var permitAll: Boolean? = null var authenticationDetailsSource: AuthenticationDetailsSource? = null - var usernameParameter: String? = null - var passwordParameter: String? = null private var defaultSuccessUrlOption: Pair? = null @@ -99,8 +95,6 @@ class FormLoginDsl { authenticationSuccessHandler?.also { login.successHandler(authenticationSuccessHandler) } authenticationFailureHandler?.also { login.failureHandler(authenticationFailureHandler) } authenticationDetailsSource?.also { login.authenticationDetailsSource(authenticationDetailsSource) } - usernameParameter?.also { login.usernameParameter(usernameParameter) } - passwordParameter?.also { login.passwordParameter(passwordParameter) } if (disabled) { login.disable() } diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt index f13ce656b15..60342d2af8c 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -707,69 +707,6 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu this.http.saml2Login(saml2LoginCustomizer) } - /** - * Configures logout support for a SAML 2.0 Service Provider.
- *
- * - * Implements the Single Logout Profile, using POST and REDIRECT bindings, as - * documented in the - * SAML V2.0 - * Core, Profiles and Bindings specifications.
- *
- * - * As a prerequisite to using this feature, is that you have a SAML v2.0 Asserting - * Party to send a logout request to. The representation of the relying party and the - * asserting party is contained within [RelyingPartyRegistration].
- *
- * - * [RelyingPartyRegistration] (s) are composed within a - * [RelyingPartyRegistrationRepository], which is required and must be - * registered with the [ApplicationContext] or configured via - * [HttpSecurityDsl.saml2Login].
- *
- * - * The default configuration provides an auto-generated logout endpoint at - * `/logout` and redirects to `/login?logout` when - * logout completes.
- *
- * - *

- *

Example Configuration

- * - * The following example shows the minimal configuration required, using a - * hypothetical asserting party. - * - * Example: - * - * ``` - * @Configuration - * @EnableWebSecurity - * class SecurityConfig { - * - * @Bean - * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - * http { - * saml2Login { - * relyingPartyRegistration = getSaml2RelyingPartyRegistration() - * } - * saml2Logout { } - * } - * return http.build() - * } - * } - * ``` - * - *

- * @param saml2LogoutConfiguration custom configuration to configure the - * SAML 2.0 service provider - * @since 6.3 - * @see [Saml2LogoutDsl] - */ - fun saml2Logout(saml2LogoutConfiguration: Saml2LogoutDsl.() -> Unit) { - val saml2LogoutCustomizer = Saml2LogoutDsl().apply(saml2LogoutConfiguration).get() - this.http.saml2Logout(saml2LogoutCustomizer) - } - /** * Configures a SAML 2.0 relying party metadata endpoint. * diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/Saml2LogoutDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/Saml2LogoutDsl.kt deleted file mode 100644 index 3a5b090b0ea..00000000000 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/Saml2LogoutDsl.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2002-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.config.annotation.web - -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer -import org.springframework.security.config.annotation.web.saml2.LogoutRequestDsl -import org.springframework.security.config.annotation.web.saml2.LogoutResponseDsl -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository - -/** - * A Kotlin DSL to configure [HttpSecurity] SAML2 logout using idiomatic Kotlin code. - * - * @author Josh Cummings - * @since 6.3 - * @property relyingPartyRegistrationRepository the [RelyingPartyRegistrationRepository] of relying parties, - * each party representing a service provider, SP and this host, and identity provider, IDP pair that - * communicate with each other. - * @property logoutUrl the logout page to begin the SLO redirect flow - */ -@SecurityMarker -class Saml2LogoutDsl { - var relyingPartyRegistrationRepository: RelyingPartyRegistrationRepository? = null - var logoutUrl: String? = null - - private var logoutRequest: ((Saml2LogoutConfigurer.LogoutRequestConfigurer) -> Unit)? = null - private var logoutResponse: ((Saml2LogoutConfigurer.LogoutResponseConfigurer) -> Unit)? = null - - /** - * Configures SAML 2.0 Logout Request components - * @param logoutRequestConfig the {@link Customizer} to provide more - * options for the {@link LogoutRequestConfigurer} - */ - fun logoutRequest(logoutRequestConfig: LogoutRequestDsl.() -> Unit) { - this.logoutRequest = LogoutRequestDsl().apply(logoutRequestConfig).get() - } - - /** - * Configures SAML 2.0 Logout Response components - * @param logoutResponseConfig the {@link Customizer} to provide more - * options for the {@link LogoutResponseConfigurer} - */ - fun logoutResponse(logoutResponseConfig: LogoutResponseDsl.() -> Unit) { - this.logoutResponse = LogoutResponseDsl().apply(logoutResponseConfig).get() - } - - internal fun get(): (Saml2LogoutConfigurer) -> Unit { - return { saml2Logout -> - relyingPartyRegistrationRepository?.also { saml2Logout.relyingPartyRegistrationRepository(relyingPartyRegistrationRepository) } - logoutUrl?.also { saml2Logout.logoutUrl(logoutUrl) } - logoutRequest?.also { saml2Logout.logoutRequest(logoutRequest) } - logoutResponse?.also { saml2Logout.logoutResponse(logoutResponse) } - } - } -} diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutRequestDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutRequestDsl.kt deleted file mode 100644 index 0a07c15fb5e..00000000000 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutRequestDsl.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.web.saml2 - -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator -import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver - -/** - * A Kotlin DSL to configure SAML 2.0 Logout Request components using idiomatic Kotlin code. - * - * @author Josh Cummings - * @since 6.3 - * @property logoutUrl The URL by which the asserting party can send a SAML 2.0 Logout Request. - * The Asserting Party should use whatever HTTP method specified in {@link RelyingPartyRegistration#getSingleLogoutServiceBindings()}. - * @property logoutRequestValidator the [Saml2LogoutRequestValidator] to use for validating incoming {@code LogoutRequest}s. - * @property logoutRequestResolver the [Saml2LogoutRequestResolver] to use for generating outgoing {@code LogoutRequest}s. - * @property logoutRequestRepository the [Saml2LogoutRequestRepository] to use for storing outgoing {@code LogoutRequest}s for - * linking to the corresponding {@code LogoutResponse} from the asserting party - */ -@Saml2SecurityMarker -class LogoutRequestDsl { - var logoutUrl = "/logout/saml2/slo" - var logoutRequestValidator: Saml2LogoutRequestValidator? = null - var logoutRequestResolver: Saml2LogoutRequestResolver? = null - var logoutRequestRepository: Saml2LogoutRequestRepository = HttpSessionLogoutRequestRepository() - - internal fun get(): (Saml2LogoutConfigurer.LogoutRequestConfigurer) -> Unit { - return { logoutRequest -> - logoutUrl.also { logoutRequest.logoutUrl(logoutUrl) } - logoutRequestValidator?.also { logoutRequest.logoutRequestValidator(logoutRequestValidator) } - logoutRequestResolver?.also { logoutRequest.logoutRequestResolver(logoutRequestResolver) } - logoutRequestRepository.also { logoutRequest.logoutRequestRepository(logoutRequestRepository) } - } - } -} diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutResponseDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutResponseDsl.kt deleted file mode 100644 index dfc360c88d8..00000000000 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutResponseDsl.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.web.saml2 - -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver - -/** - * A Kotlin DSL to configure SAML 2.0 Logout Response components using idiomatic Kotlin code. - * - * @author Josh Cummings - * @since 6.3 - * @property logoutUrl The URL by which the asserting party can send a SAML 2.0 Logout Response. - * The Asserting Party should use whatever HTTP method specified in {@link RelyingPartyRegistration#getSingleLogoutServiceBindings()}. - * @property logoutResponseValidator the [Saml2LogoutResponseValidator] to use for validating incoming {@code LogoutResponse}s. - * @property logoutResponseResolver the [Saml2LogoutResponseResolver] to use for generating outgoing {@code LogoutResponse}s. - */ -@Saml2SecurityMarker -class LogoutResponseDsl { - var logoutUrl = "/logout/saml2/slo" - var logoutResponseValidator: Saml2LogoutResponseValidator? = null - var logoutResponseResolver: Saml2LogoutResponseResolver? = null - - internal fun get(): (Saml2LogoutConfigurer.LogoutResponseConfigurer) -> Unit { - return { logoutResponse -> - logoutUrl.also { logoutResponse.logoutUrl(logoutUrl) } - logoutResponseValidator?.also { logoutResponse.logoutResponseValidator(logoutResponseValidator) } - logoutResponseResolver?.also { logoutResponse.logoutResponseResolver(logoutResponseResolver) } - } - } -} diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/Saml2SecurityMarker.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/Saml2SecurityMarker.kt deleted file mode 100644 index bcf8df5b813..00000000000 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/Saml2SecurityMarker.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.web.saml2 - -/** - * Marker annotation indicating that the annotated class is part of the SAML 2.0 logout security DSL. - * - * @author Josh Cummings - * @since 6.3 - */ -@DslMarker -annotation class Saml2SecurityMarker diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDsl.kt index 639130f17fd..300a3d6a60f 100644 --- a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDsl.kt @@ -682,36 +682,6 @@ class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val in this.http.oidcLogout(oidcLogoutCustomizer) } - /** - * Configures Session Management support. - * - * Example: - * - * ``` - * @Configuration - * @EnableWebFluxSecurity - * open class SecurityConfig { - * - * @Bean - * open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - * return http { - * sessionManagement { - * sessionConcurrency { } - * } - * } - * } - * } - * ``` - * - * @param sessionManagementConfig custom configuration to configure the Session Management - * @since 6.3 - * @see [ServerSessionManagementDsl] - */ - fun sessionManagement(sessionManagementConfig: ServerSessionManagementDsl.() -> Unit) { - val sessionManagementCustomizer = ServerSessionManagementDsl().apply(sessionManagementConfig).get() - this.http.sessionManagement(sessionManagementCustomizer) - } - /** * Apply all configurations to the provided [ServerHttpSecurity] */ diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerSessionConcurrencyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerSessionConcurrencyDsl.kt deleted file mode 100644 index b2dad3a574d..00000000000 --- a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerSessionConcurrencyDsl.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2002-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.config.web.server - -import org.springframework.security.core.session.ReactiveSessionRegistry -import org.springframework.security.web.server.authentication.ServerMaximumSessionsExceededHandler -import org.springframework.security.web.server.authentication.SessionLimit - -/** - * A Kotlin DSL to configure [ServerHttpSecurity] Session Concurrency support using idiomatic Kotlin code. - * - * @author Marcus da Coregio - * @since 6.3 - */ -@ServerSecurityMarker -class ServerSessionConcurrencyDsl { - var maximumSessions: SessionLimit? = null - var maximumSessionsExceededHandler: ServerMaximumSessionsExceededHandler? = null - var sessionRegistry: ReactiveSessionRegistry? = null - - internal fun get(): (ServerHttpSecurity.SessionManagementSpec.ConcurrentSessionsSpec) -> Unit { - return { sessionConcurrency -> - maximumSessions?.also { - sessionConcurrency.maximumSessions(maximumSessions!!) - } - maximumSessionsExceededHandler?.also { - sessionConcurrency.maximumSessionsExceededHandler(maximumSessionsExceededHandler!!) - } - sessionRegistry?.also { - sessionConcurrency.sessionRegistry(sessionRegistry!!) - } - } - } -} diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerSessionManagementDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerSessionManagementDsl.kt deleted file mode 100644 index 4858baa2440..00000000000 --- a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerSessionManagementDsl.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2002-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.config.web.server - -/** - * A Kotlin DSL to configure [ServerHttpSecurity] Session Management using idiomatic Kotlin code. - * - * @author Marcus da Coregio - * @since 6.3 - */ -@ServerSecurityMarker -class ServerSessionManagementDsl { - private var sessionConcurrency: ((ServerHttpSecurity.SessionManagementSpec.ConcurrentSessionsSpec) -> Unit)? = null - - /** - * Enables Session Management support. - * - * Example: - * - * ``` - * @Configuration - * @EnableWebFluxSecurity - * open class SecurityConfig { - * - * @Bean - * open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - * return http { - * sessionManagement { - * sessionConcurrency { - * maximumSessions = { authentication -> Mono.just(1) } - * } - * } - * } - * } - * } - * ``` - * - * @param backChannelConfig custom configurations to configure OIDC 1.0 Back-Channel Logout support - * @see [ServerOidcBackChannelLogoutDsl] - */ - fun sessionConcurrency(sessionConcurrencyConfig: ServerSessionConcurrencyDsl.() -> Unit) { - this.sessionConcurrency = ServerSessionConcurrencyDsl().apply(sessionConcurrencyConfig).get() - } - - internal fun get(): (ServerHttpSecurity.SessionManagementSpec) -> Unit { - return { sessionManagement -> - sessionConcurrency?.also { sessionManagement.concurrentSessions(sessionConcurrency) } - } - } -} diff --git a/config/src/main/resources/META-INF/spring.schemas b/config/src/main/resources/META-INF/spring.schemas index 90821ecf714..f25b5f682b5 100644 --- a/config/src/main/resources/META-INF/spring.schemas +++ b/config/src/main/resources/META-INF/spring.schemas @@ -1,5 +1,4 @@ -http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.3.xsd -http\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd +http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.2.xsd http\://www.springframework.org/schema/security/spring-security-6.2.xsd=org/springframework/security/config/spring-security-6.2.xsd http\://www.springframework.org/schema/security/spring-security-6.1.xsd=org/springframework/security/config/spring-security-6.1.xsd http\://www.springframework.org/schema/security/spring-security-6.0.xsd=org/springframework/security/config/spring-security-6.0.xsd @@ -23,8 +22,7 @@ http\://www.springframework.org/schema/security/spring-security-2.0.xsd=org/spri http\://www.springframework.org/schema/security/spring-security-2.0.1.xsd=org/springframework/security/config/spring-security-2.0.1.xsd http\://www.springframework.org/schema/security/spring-security-2.0.2.xsd=org/springframework/security/config/spring-security-2.0.2.xsd http\://www.springframework.org/schema/security/spring-security-2.0.4.xsd=org/springframework/security/config/spring-security-2.0.4.xsd -https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.3.xsd -https\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd +https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.2.xsd https\://www.springframework.org/schema/security/spring-security-6.2.xsd=org/springframework/security/config/spring-security-6.2.xsd https\://www.springframework.org/schema/security/spring-security-6.1.xsd=org/springframework/security/config/spring-security-6.1.xsd https\://www.springframework.org/schema/security/spring-security-6.0.xsd=org/springframework/security/config/spring-security-6.0.xsd diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.3.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-6.3.rnc deleted file mode 100644 index ab0c881b848..00000000000 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.3.rnc +++ /dev/null @@ -1,1349 +0,0 @@ -namespace a = "https://relaxng.org/ns/compatibility/annotations/1.0" -datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes" - -default namespace = "http://www.springframework.org/schema/security" - -start = http | ldap-server | authentication-provider | ldap-authentication-provider | any-user-service | ldap-server | ldap-authentication-provider - -hash = - ## Defines the hashing algorithm used on user passwords. Bcrypt is recommended. - attribute hash {"bcrypt"} -base64 = - ## Whether a string should be base64 encoded - attribute base64 {xsd:boolean} -request-matcher = - ## Defines the strategy use for matching incoming requests. Currently the options are 'mvc' (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions. - attribute request-matcher {"mvc" | "ant" | "regex" | "ciRegex"} -port = - ## Specifies an IP port number. Used to configure an embedded LDAP server, for example. - attribute port { xsd:nonNegativeInteger } -url = - ## Specifies a URL. - attribute url { xsd:token } -id = - ## A bean identifier, used for referring to the bean elsewhere in the context. - attribute id {xsd:token} -name = - ## A bean identifier, used for referring to the bean elsewhere in the context. - attribute name {xsd:token} -ref = - ## Defines a reference to a Spring bean Id. - attribute ref {xsd:token} - -cache-ref = - ## Defines a reference to a cache for use with a UserDetailsService. - attribute cache-ref {xsd:token} - -user-service-ref = - ## A reference to a user-service (or UserDetailsService bean) Id - attribute user-service-ref {xsd:token} - -authentication-manager-ref = - ## A reference to an AuthenticationManager bean - attribute authentication-manager-ref {xsd:token} - -data-source-ref = - ## A reference to a DataSource bean - attribute data-source-ref {xsd:token} - - - -debug = - ## Enables Spring Security debugging infrastructure. This will provide human-readable (multi-line) debugging information to monitor requests coming into the security filters. This may include sensitive information, such as request parameters or headers, and should only be used in a development environment. - element debug {empty} - -password-encoder = - ## element which defines a password encoding strategy. Used by an authentication provider to convert submitted passwords to hashed versions, for example. - element password-encoder {password-encoder.attlist} -password-encoder.attlist &= - ref | (hash) - -role-prefix = - ## A non-empty string prefix that will be added to role strings loaded from persistent storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is non-empty. - attribute role-prefix {xsd:token} - -use-expressions = - ## Enables the use of expressions in the 'access' attributes in elements rather than the traditional list of configuration attributes. Defaults to 'true'. If enabled, each attribute should contain a single boolean expression. If the expression evaluates to 'true', access will be granted. - attribute use-expressions {xsd:boolean} - -ldap-server = - ## Defines an LDAP server location or starts an embedded server. The url indicates the location of a remote server. If no url is given, an embedded server will be started, listening on the supplied port number. The port is optional and defaults to 33389. A Spring LDAP ContextSource bean will be registered for the server with the id supplied. - element ldap-server {ldap-server.attlist} -ldap-server.attlist &= id? -ldap-server.attlist &= (url | port)? -ldap-server.attlist &= - ## Username (DN) of the "manager" user identity which will be used to authenticate to a (non-embedded) LDAP server. If omitted, anonymous access will be used. - attribute manager-dn {xsd:string}? -ldap-server.attlist &= - ## The password for the manager DN. This is required if the manager-dn is specified. - attribute manager-password {xsd:string}? -ldap-server.attlist &= - ## Explicitly specifies an ldif file resource to load into an embedded LDAP server. The default is classpath*:*.ldiff - attribute ldif { xsd:string }? -ldap-server.attlist &= - ## Optional root suffix for the embedded LDAP server. Default is "dc=springframework,dc=org" - attribute root { xsd:string }? -ldap-server.attlist &= - ## Explicitly specifies which embedded ldap server should use. Values are 'apacheds' and 'unboundid'. By default, it will depends if the library is available in the classpath. - attribute mode { "apacheds" | "unboundid" }? - -ldap-server-ref-attribute = - ## The optional server to use. If omitted, and a default LDAP server is registered (using with no Id), that server will be used. - attribute server-ref {xsd:token} - - -group-search-filter-attribute = - ## Group search filter. Defaults to (uniqueMember={0}). The substituted parameter is the DN of the user. - attribute group-search-filter {xsd:token} -group-search-base-attribute = - ## Search base for group membership searches. Defaults to "" (searching from the root). - attribute group-search-base {xsd:token} -user-search-filter-attribute = - ## The LDAP filter used to search for users (optional). For example "(uid={0})". The substituted parameter is the user's login name. - attribute user-search-filter {xsd:token} -user-search-base-attribute = - ## Search base for user searches. Defaults to "". Only used with a 'user-search-filter'. - attribute user-search-base {xsd:token} -group-role-attribute-attribute = - ## The LDAP attribute name which contains the role name which will be used within Spring Security. Defaults to "cn". - attribute group-role-attribute {xsd:token} -user-details-class-attribute = - ## Allows the objectClass of the user entry to be specified. If set, the framework will attempt to load standard attributes for the defined class into the returned UserDetails object - attribute user-details-class {"person" | "inetOrgPerson"} -user-context-mapper-attribute = - ## Allows explicit customization of the loaded user object by specifying a UserDetailsContextMapper bean which will be called with the context information from the user's directory entry - attribute user-context-mapper-ref {xsd:token} - - -ldap-user-service = - ## This element configures a LdapUserDetailsService which is a combination of a FilterBasedLdapUserSearch and a DefaultLdapAuthoritiesPopulator. - element ldap-user-service {ldap-us.attlist} -ldap-us.attlist &= id? -ldap-us.attlist &= - ldap-server-ref-attribute? -ldap-us.attlist &= - user-search-filter-attribute? -ldap-us.attlist &= - user-search-base-attribute? -ldap-us.attlist &= - group-search-filter-attribute? -ldap-us.attlist &= - group-search-base-attribute? -ldap-us.attlist &= - group-role-attribute-attribute? -ldap-us.attlist &= - cache-ref? -ldap-us.attlist &= - role-prefix? -ldap-us.attlist &= - (user-details-class-attribute | user-context-mapper-attribute)? - -ldap-authentication-provider = - ## Sets up an ldap authentication provider - element ldap-authentication-provider {ldap-ap.attlist, password-compare-element?} -ldap-ap.attlist &= - ldap-server-ref-attribute? -ldap-ap.attlist &= - user-search-base-attribute? -ldap-ap.attlist &= - user-search-filter-attribute? -ldap-ap.attlist &= - group-search-base-attribute? -ldap-ap.attlist &= - group-search-filter-attribute? -ldap-ap.attlist &= - group-role-attribute-attribute? -ldap-ap.attlist &= - ## A specific pattern used to build the user's DN, for example "uid={0},ou=people". The key "{0}" must be present and will be substituted with the username. - attribute user-dn-pattern {xsd:token}? -ldap-ap.attlist &= - role-prefix? -ldap-ap.attlist &= - (user-details-class-attribute | user-context-mapper-attribute)? - -password-compare-element = - ## Specifies that an LDAP provider should use an LDAP compare operation of the user's password to authenticate the user - element password-compare {password-compare.attlist, password-encoder?} - -password-compare.attlist &= - ## The attribute in the directory which contains the user password. Defaults to "userPassword". - attribute password-attribute {xsd:token}? -password-compare.attlist &= - hash? - -intercept-methods = - ## Can be used inside a bean definition to add a security interceptor to the bean and set up access configuration attributes for the bean's methods - element intercept-methods {intercept-methods.attlist, protect+} -intercept-methods.attlist &= - ## Optional AccessDecisionManager bean ID to be used by the created method security interceptor. - attribute access-decision-manager-ref {xsd:token}? -intercept-methods.attlist &= - ## Use the AuthorizationManager API instead of AccessDecisionManager (defaults to true) - attribute use-authorization-manager {xsd:boolean}? -intercept-methods.attlist &= - ## Use this AuthorizationManager instead of the default (supercedes use-authorization-manager) - attribute authorization-manager-ref {xsd:token}? - -protect = - ## Defines a protected method and the access control configuration attributes that apply to it. We strongly advise you NOT to mix "protect" declarations with any services provided "global-method-security". - element protect {protect.attlist, empty} -protect.attlist &= - ## A method name - attribute method {xsd:token} -protect.attlist &= - ## Access configuration attributes list that applies to the method, e.g. "ROLE_A,ROLE_B". - attribute access {xsd:token} - -method-security-metadata-source = - ## Creates a MethodSecurityMetadataSource instance - element method-security-metadata-source {msmds.attlist, protect+} -msmds.attlist &= id? - -msmds.attlist &= use-expressions? - -method-security = - ## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with Spring Security annotations. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. Interceptors are invoked in the order specified in AuthorizationInterceptorsOrder. Use can create your own interceptors using Spring AOP. Also, annotation-based interception can be overridden by expressions listed in elements. - element method-security {method-security.attlist, expression-handler?, protect-pointcut*} -method-security.attlist &= - ## Specifies whether the use of Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this application context. Defaults to "true". - attribute pre-post-enabled {xsd:boolean}? -method-security.attlist &= - ## Specifies whether the use of Spring Security's @Secured annotations should be enabled for this application context. Defaults to "false". - attribute secured-enabled {xsd:boolean}? -method-security.attlist &= - ## Specifies whether JSR-250 style attributes are to be used (for example "RolesAllowed"). This will require the javax.annotation.security classes on the classpath. Defaults to "false". - attribute jsr250-enabled {xsd:boolean}? -method-security.attlist &= - ## If true, class-based proxying will be used instead of interface-based proxying. - attribute proxy-target-class {xsd:boolean}? -method-security.attlist &= - ## If set to aspectj, then use AspectJ to intercept method invocation - attribute mode {"aspectj"}? -method-security.attlist &= - ## Specifies the security context holder strategy to use, by default uses a ThreadLocal-based strategy - attribute security-context-holder-strategy-ref {xsd:string}? -method-security.attlist &= - ## Use this ObservationRegistry to collect metrics on various parts of the filter chain - attribute observation-registry-ref {xsd:token}? - -global-method-security = - ## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with the ordered list of "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. If you use and enable all four sources of method security metadata (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250 security annotations), the metadata sources will be queried in that order. In practical terms, this enables you to use XML to override method security metadata expressed in annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize etc.), @Secured and finally JSR-250. - element global-method-security {global-method-security.attlist, (pre-post-annotation-handling | expression-handler)?, protect-pointcut*, after-invocation-provider*} -global-method-security.attlist &= - ## Specifies whether the use of Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this application context. Defaults to "disabled". - attribute pre-post-annotations {"disabled" | "enabled" }? -global-method-security.attlist &= - ## Specifies whether the use of Spring Security's @Secured annotations should be enabled for this application context. Defaults to "disabled". - attribute secured-annotations {"disabled" | "enabled" }? -global-method-security.attlist &= - ## Specifies whether JSR-250 style attributes are to be used (for example "RolesAllowed"). This will require the javax.annotation.security classes on the classpath. Defaults to "disabled". - attribute jsr250-annotations {"disabled" | "enabled" }? -global-method-security.attlist &= - ## Optional AccessDecisionManager bean ID to override the default used for method security. - attribute access-decision-manager-ref {xsd:token}? -global-method-security.attlist &= - ## Optional RunAsmanager implementation which will be used by the configured MethodSecurityInterceptor - attribute run-as-manager-ref {xsd:token}? -global-method-security.attlist &= - ## Allows the advice "order" to be set for the method security interceptor. - attribute order {xsd:token}? -global-method-security.attlist &= - ## If true, class based proxying will be used instead of interface based proxying. - attribute proxy-target-class {xsd:boolean}? -global-method-security.attlist &= - ## Can be used to specify that AspectJ should be used instead of the default Spring AOP. If set, secured classes must be woven with the AnnotationSecurityAspect from the spring-security-aspects module. - attribute mode {"aspectj"}? -global-method-security.attlist &= - ## An external MethodSecurityMetadataSource instance can be supplied which will take priority over other sources (such as the default annotations). - attribute metadata-source-ref {xsd:token}? -global-method-security.attlist &= - authentication-manager-ref? - - -after-invocation-provider = - ## Allows addition of extra AfterInvocationProvider beans which should be called by the MethodSecurityInterceptor created by global-method-security. - element after-invocation-provider {ref} - -pre-post-annotation-handling = - ## Allows the default expression-based mechanism for handling Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) to be replace entirely. Only applies if these annotations are enabled. - element pre-post-annotation-handling {invocation-attribute-factory, pre-invocation-advice, post-invocation-advice} - -invocation-attribute-factory = - ## Defines the PrePostInvocationAttributeFactory instance which is used to generate pre and post invocation metadata from the annotated methods. - element invocation-attribute-factory {ref} - -pre-invocation-advice = - ## Customizes the PreInvocationAuthorizationAdviceVoter with the ref as the PreInvocationAuthorizationAdviceVoter for the element. - element pre-invocation-advice {ref} - -post-invocation-advice = - ## Customizes the PostInvocationAdviceProvider with the ref as the PostInvocationAuthorizationAdvice for the element. - element post-invocation-advice {ref} - - -expression-handler = - ## Defines the SecurityExpressionHandler instance which will be used if expression-based access-control is enabled. A default implementation (with no ACL support) will be used if not supplied. - element expression-handler {ref} - -protect-pointcut = - ## Defines a protected pointcut and the access control configuration attributes that apply to it. Every bean registered in the Spring application context that provides a method that matches the pointcut will receive security authorization. - element protect-pointcut {protect-pointcut.attlist, empty} -protect-pointcut.attlist &= - ## An AspectJ expression, including the 'execution' keyword. For example, 'execution(int com.foo.TargetObject.countLength(String))' (without the quotes). - attribute expression {xsd:string} -protect-pointcut.attlist &= - ## Access configuration attributes list that applies to all methods matching the pointcut, e.g. "ROLE_A,ROLE_B" - attribute access {xsd:token} - -websocket-message-broker = - ## Allows securing a Message Broker. There are two modes. If no id is specified: ensures that any SimpAnnotationMethodMessageHandler has the AuthenticationPrincipalArgumentResolver registered as a custom argument resolver; ensures that the SecurityContextChannelInterceptor is automatically registered for the clientInboundChannel; and that a ChannelSecurityInterceptor is registered with the clientInboundChannel. If the id is specified, creates a ChannelSecurityInterceptor that can be manually registered with the clientInboundChannel. - element websocket-message-broker { websocket-message-broker.attrlist, (intercept-message* & expression-handler?) } - -websocket-message-broker.attrlist &= - ## A bean identifier, used for referring to the bean elsewhere in the context. If specified, explicit configuration within clientInboundChannel is required. If not specified, ensures that any SimpAnnotationMethodMessageHandler has the AuthenticationPrincipalArgumentResolver registered as a custom argument resolver; ensures that the SecurityContextChannelInterceptor is automatically registered for the clientInboundChannel; and that a ChannelSecurityInterceptor is registered with the clientInboundChannel. - attribute id {xsd:token}? -websocket-message-broker.attrlist &= - ## Disables the requirement for CSRF token to be present in the Stomp headers (default false). Changing the default is useful if it is necessary to allow other origins to make SockJS connections. - attribute same-origin-disabled {xsd:boolean}? -websocket-message-broker.attrlist &= - ## Use this AuthorizationManager instead of deriving one from elements - attribute authorization-manager-ref {xsd:string}? -websocket-message-broker.attrlist &= - ## Use AuthorizationManager API instead of SecurityMetadatasource (defaults to true) - attribute use-authorization-manager {xsd:boolean}? -websocket-message-broker.attrlist &= - ## Use this SecurityContextHolderStrategy (note only supported in conjunction with the AuthorizationManager API) - attribute security-context-holder-strategy-ref {xsd:string}? - -intercept-message = - ## Creates an authorization rule for a websocket message. - element intercept-message {intercept-message.attrlist} - -intercept-message.attrlist &= - ## The destination ant pattern which will be mapped to the access attribute. For example, /** matches any message with a destination, /admin/** matches any message that has a destination that starts with admin. - attribute pattern {xsd:token}? -intercept-message.attrlist &= - ## The access configuration attributes that apply for the configured message. For example, permitAll grants access to anyone, hasRole('ROLE_ADMIN') requires the user have the role 'ROLE_ADMIN'. - attribute access {xsd:token}? -intercept-message.attrlist &= - ## The type of message to match on. Valid values are defined in SimpMessageType (i.e. CONNECT, CONNECT_ACK, HEARTBEAT, MESSAGE, SUBSCRIBE, UNSUBSCRIBE, DISCONNECT, DISCONNECT_ACK, OTHER). - attribute type {"CONNECT" | "CONNECT_ACK" | "HEARTBEAT" | "MESSAGE" | "SUBSCRIBE"| "UNSUBSCRIBE" | "DISCONNECT" | "DISCONNECT_ACK" | "OTHER"}? - -http-firewall = - ## Allows a custom instance of HttpFirewall to be injected into the FilterChainProxy created by the namespace. - element http-firewall {ref} - -http = - ## Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "security" attribute to "none". - element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & oauth2-login? & oauth2-client? & oauth2-resource-server? & saml2-login? & saml2-logout? & x509? & jee? & http-basic? & logout? & password-management? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers? & csrf? & cors?) } -http.attlist &= - ## The request URL pattern which will be mapped to the filter chain created by this element. If omitted, the filter chain will match all requests. - attribute pattern {xsd:token}? -http.attlist &= - ## When set to 'none', requests matching the pattern attribute will be ignored by Spring Security. No security filters will be applied and no SecurityContext will be available. If set, the element must be empty, with no children. - attribute security {"none"}? -http.attlist &= - ## Allows a RequestMatcher instance to be used, as an alternative to pattern-matching. - attribute request-matcher-ref { xsd:token }? -http.attlist &= - ## A legacy attribute which automatically registers a login form, BASIC authentication and a logout URL and logout services. If unspecified, defaults to "false". We'd recommend you avoid using this and instead explicitly configure the services you require. - attribute auto-config {xsd:boolean}? -http.attlist &= - use-expressions? -http.attlist &= - ## A reference to a SecurityContextHolderStrategy bean. This can be used to customize how the SecurityContextHolder is stored during a request - attribute security-context-holder-strategy-ref {xsd:token}? -http.attlist &= - ## Controls the eagerness with which an HTTP session is created by Spring Security classes. If not set, defaults to "ifRequired". If "stateless" is used, this implies that the application guarantees that it will not create a session. This differs from the use of "never" which means that Spring Security will not create a session, but will make use of one if the application does. - attribute create-session {"ifRequired" | "always" | "never" | "stateless"}? -http.attlist &= - ## A reference to a SecurityContextRepository bean. This can be used to customize how the SecurityContext is stored between requests. - attribute security-context-repository-ref {xsd:token}? -http.attlist &= - ## Optional attribute that specifies that the SecurityContext should require explicit saving rather than being synchronized from the SecurityContextHolder. Defaults to "true". - attribute security-context-explicit-save {xsd:boolean}? -http.attlist &= - request-matcher? -http.attlist &= - ## Provides versions of HttpServletRequest security methods such as isUserInRole() and getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to "true". - attribute servlet-api-provision {xsd:boolean}? -http.attlist &= - ## If available, runs the request as the Subject acquired from the JaasAuthenticationToken. Defaults to "false". - attribute jaas-api-provision {xsd:boolean}? -http.attlist &= - ## Use AuthorizationManager API instead of SecurityMetadataSource (defaults to true) - attribute use-authorization-manager {xsd:boolean}? -http.attlist &= - ## Use this AuthorizationManager instead of deriving one from elements - attribute authorization-manager-ref {xsd:token}? -http.attlist &= - ## Optional attribute specifying the ID of the AccessDecisionManager implementation which should be used for authorizing HTTP requests. - attribute access-decision-manager-ref {xsd:token}? -http.attlist &= - ## Optional attribute specifying the realm name that will be used for all authentication features that require a realm name (eg BASIC and Digest authentication). If unspecified, defaults to "Spring Security Application". - attribute realm {xsd:token}? -http.attlist &= - ## Allows a customized AuthenticationEntryPoint to be set on the ExceptionTranslationFilter. - attribute entry-point-ref {xsd:token}? -http.attlist &= - ## Corresponds to the observeOncePerRequest property of FilterSecurityInterceptor. Defaults to "false" - attribute once-per-request {xsd:boolean}? -http.attlist &= - ## Corresponds to the shouldFilterAllDispatcherTypes property of AuthorizationFilter. Do not work when use-authorization-manager=false. Defaults to "true". - attribute filter-all-dispatcher-types {xsd:boolean}? -http.attlist &= - ## Prevents the jsessionid parameter from being added to rendered URLs. Defaults to "true" (rewriting is disabled). - attribute disable-url-rewriting {xsd:boolean}? -http.attlist &= - ## Exposes the list of filters defined by this configuration under this bean name in the application context. - name? -http.attlist &= - authentication-manager-ref? -http.attlist &= - ## Use this ObservationRegistry to collect metrics on various parts of the filter chain - attribute observation-registry-ref {xsd:token}? - -access-denied-handler = - ## Defines the access-denied strategy that should be used. An access denied page can be defined or a reference to an AccessDeniedHandler instance. - element access-denied-handler {access-denied-handler.attlist, empty} -access-denied-handler.attlist &= (ref | access-denied-handler-page) - -access-denied-handler-page = - ## The access denied page that an authenticated user will be redirected to if they request a page which they don't have the authority to access. - attribute error-page {xsd:token} - -intercept-url = - ## Specifies the access attributes and/or filter list for a particular set of URLs. - element intercept-url {intercept-url.attlist, empty} -intercept-url.attlist &= - (pattern | request-matcher-ref) -intercept-url.attlist &= - ## The access configuration attributes that apply for the configured path. - attribute access {xsd:token}? -intercept-url.attlist &= - ## The HTTP Method for which the access configuration attributes should apply. If not specified, the attributes will apply to any method. - attribute method {"GET" | "DELETE" | "HEAD" | "OPTIONS" | "POST" | "PUT" | "PATCH" | "TRACE"}? - -intercept-url.attlist &= - ## Used to specify that a URL must be accessed over http or https, or that there is no preference. The value should be "http", "https" or "any", respectively. - attribute requires-channel {xsd:token}? -intercept-url.attlist &= - ## The path to the servlet. This attribute is only applicable when 'request-matcher' is 'mvc'. In addition, the value is only required in the following 2 use cases: 1) There are 2 or more HttpServlet's registered in the ServletContext that have mappings starting with '/' and are different; 2) The pattern starts with the same value of a registered HttpServlet path, excluding the default (root) HttpServlet '/'. - attribute servlet-path {xsd:token}? - -logout = - ## Incorporates a logout processing filter. Most web applications require a logout filter, although you may not require one if you write a controller to provider similar logic. - element logout {logout.attlist, empty} -logout.attlist &= - ## Specifies the URL that will cause a logout. Spring Security will initialize a filter that responds to this particular URL. Defaults to /logout if unspecified. - attribute logout-url {xsd:token}? -logout.attlist &= - ## Specifies the URL to display once the user has logged out. If not specified, defaults to /?logout (i.e. /login?logout). - attribute logout-success-url {xsd:token}? -logout.attlist &= - ## Specifies whether a logout also causes HttpSession invalidation, which is generally desirable. If unspecified, defaults to true. - attribute invalidate-session {xsd:boolean}? -logout.attlist &= - ## A reference to a LogoutSuccessHandler implementation which will be used to determine the destination to which the user is taken after logging out. - attribute success-handler-ref {xsd:token}? -logout.attlist &= - ## A comma-separated list of the names of cookies which should be deleted when the user logs out - attribute delete-cookies {xsd:token}? - -request-cache = - ## Allow the RequestCache used for saving requests during the login process to be set - element request-cache {ref} - -form-login = - ## Sets up a form login configuration for authentication with a username and password - element form-login {form-login.attlist, empty} -form-login.attlist &= - ## The URL that the login form is posted to. If unspecified, it defaults to /login. - attribute login-processing-url {xsd:token}? -form-login.attlist &= - ## The name of the request parameter which contains the username. Defaults to 'username'. - attribute username-parameter {xsd:token}? -form-login.attlist &= - ## The name of the request parameter which contains the password. Defaults to 'password'. - attribute password-parameter {xsd:token}? -form-login.attlist &= - ## The URL that will be redirected to after successful authentication, if the user's previous action could not be resumed. This generally happens if the user visits a login page without having first requested a secured operation that triggers authentication. If unspecified, defaults to the root of the application. - attribute default-target-url {xsd:token}? -form-login.attlist &= - ## Whether the user should always be redirected to the default-target-url after login. - attribute always-use-default-target {xsd:boolean}? -form-login.attlist &= - ## The URL for the login page. If no login URL is specified, Spring Security will automatically create a login URL at GET /login and a corresponding filter to render that login URL when requested. - attribute login-page {xsd:token}? -form-login.attlist &= - ## The URL for the login failure page. If no login failure URL is specified, Spring Security will automatically create a failure login URL at /login?error and a corresponding filter to render that login failure URL when requested. - attribute authentication-failure-url {xsd:token}? -form-login.attlist &= - ## Reference to an AuthenticationSuccessHandler bean which should be used to handle a successful authentication request. Should not be used in combination with default-target-url (or always-use-default-target-url) as the implementation should always deal with navigation to the subsequent destination - attribute authentication-success-handler-ref {xsd:token}? -form-login.attlist &= - ## Reference to an AuthenticationFailureHandler bean which should be used to handle a failed authentication request. Should not be used in combination with authentication-failure-url as the implementation should always deal with navigation to the subsequent destination - attribute authentication-failure-handler-ref {xsd:token}? -form-login.attlist &= - ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter - attribute authentication-details-source-ref {xsd:token}? -form-login.attlist &= - ## The URL for the ForwardAuthenticationFailureHandler - attribute authentication-failure-forward-url {xsd:token}? -form-login.attlist &= - ## The URL for the ForwardAuthenticationSuccessHandler - attribute authentication-success-forward-url {xsd:token}? - -oauth2-login = - ## Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider. - element oauth2-login {oauth2-login.attlist} -oauth2-login.attlist &= - ## Reference to the ClientRegistrationRepository - attribute client-registration-repository-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the OAuth2AuthorizedClientRepository - attribute authorized-client-repository-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the OAuth2AuthorizedClientService - attribute authorized-client-service-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the AuthorizationRequestRepository - attribute authorization-request-repository-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the OAuth2AuthorizationRequestResolver - attribute authorization-request-resolver-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the authorization RedirectStrategy - attribute authorization-redirect-strategy-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the OAuth2AccessTokenResponseClient - attribute access-token-response-client-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the GrantedAuthoritiesMapper - attribute user-authorities-mapper-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the OAuth2UserService - attribute user-service-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the OpenID Connect OAuth2UserService - attribute oidc-user-service-ref {xsd:token}? -oauth2-login.attlist &= - ## The URI where the filter processes authentication requests - attribute login-processing-url {xsd:token}? -oauth2-login.attlist &= - ## The URI to send users to login - attribute login-page {xsd:token}? -oauth2-login.attlist &= - ## Reference to the AuthenticationSuccessHandler - attribute authentication-success-handler-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the AuthenticationFailureHandler - attribute authentication-failure-handler-ref {xsd:token}? -oauth2-login.attlist &= - ## Reference to the JwtDecoderFactory used by OidcAuthorizationCodeAuthenticationProvider - attribute jwt-decoder-factory-ref {xsd:token}? - -oauth2-client = - ## Configures OAuth 2.0 Client support. - element oauth2-client {oauth2-client.attlist, (authorization-code-grant?) } -oauth2-client.attlist &= - ## Reference to the ClientRegistrationRepository - attribute client-registration-repository-ref {xsd:token}? -oauth2-client.attlist &= - ## Reference to the OAuth2AuthorizedClientRepository - attribute authorized-client-repository-ref {xsd:token}? -oauth2-client.attlist &= - ## Reference to the OAuth2AuthorizedClientService - attribute authorized-client-service-ref {xsd:token}? - -authorization-code-grant = - ## Configures OAuth 2.0 Authorization Code Grant. - element authorization-code-grant {authorization-code-grant.attlist, empty} -authorization-code-grant.attlist &= - ## Reference to the AuthorizationRequestRepository - attribute authorization-request-repository-ref {xsd:token}? -authorization-code-grant.attlist &= - ## Reference to the authorization RedirectStrategy - attribute authorization-redirect-strategy-ref {xsd:token}? -authorization-code-grant.attlist &= - ## Reference to the OAuth2AuthorizationRequestResolver - attribute authorization-request-resolver-ref {xsd:token}? -authorization-code-grant.attlist &= - ## Reference to the OAuth2AccessTokenResponseClient - attribute access-token-response-client-ref {xsd:token}? - -client-registrations = - ## Container element for client(s) registered with an OAuth 2.0 or OpenID Connect 1.0 Provider. - element client-registrations {client-registration+, provider*} - -client-registration = - ## Represents a client registered with an OAuth 2.0 or OpenID Connect 1.0 Provider. - element client-registration {client-registration.attlist} -client-registration.attlist &= - ## The ID that uniquely identifies the client registration. - attribute registration-id {xsd:token} -client-registration.attlist &= - ## The client identifier. - attribute client-id {xsd:token} -client-registration.attlist &= - ## The client secret. - attribute client-secret {xsd:token}? -client-registration.attlist &= - ## The method used to authenticate the client with the provider. The supported values are client_secret_basic, client_secret_post and none (public clients). - attribute client-authentication-method {"client_secret_basic" | "basic" | "client_secret_post" | "post" | "none"}? -client-registration.attlist &= - ## The OAuth 2.0 Authorization Framework defines four Authorization Grant types. The supported values are authorization_code, client_credentials and password. - attribute authorization-grant-type {"authorization_code" | "client_credentials" | "password"}? -client-registration.attlist &= - ## The client’s registered redirect URI that the Authorization Server redirects the end-user’s user-agent to after the end-user has authenticated and authorized access to the client. - attribute redirect-uri {xsd:token}? -client-registration.attlist &= - ## A comma-separated list of scope(s) requested by the client during the Authorization Request flow, such as openid, email, or profile. - attribute scope {xsd:token}? -client-registration.attlist &= - ## A descriptive name used for the client. The name may be used in certain scenarios, such as when displaying the name of the client in the auto-generated login page. - attribute client-name {xsd:token}? -client-registration.attlist &= - ## A reference to the associated provider. May reference a 'provider' element or use one of the common providers (google, github, facebook, okta). - attribute provider-id {xsd:token} - -provider = - ## The configuration information for an OAuth 2.0 or OpenID Connect 1.0 Provider. - element provider {provider.attlist} -provider.attlist &= - ## The ID that uniquely identifies the provider. - attribute provider-id {xsd:token} -provider.attlist &= - ## The Authorization Endpoint URI for the Authorization Server. - attribute authorization-uri {xsd:token}? -provider.attlist &= - ## The Token Endpoint URI for the Authorization Server. - attribute token-uri {xsd:token}? -provider.attlist &= - ## The UserInfo Endpoint URI used to access the claims/attributes of the authenticated end-user. - attribute user-info-uri {xsd:token}? -provider.attlist &= - ## The authentication method used when sending the access token to the UserInfo Endpoint. The supported values are header, form and query. - attribute user-info-authentication-method {"header" | "form" | "query"}? -provider.attlist &= - ## The name of the attribute returned in the UserInfo Response that references the Name or Identifier of the end-user. - attribute user-info-user-name-attribute {xsd:token}? -provider.attlist &= - ## The URI used to retrieve the JSON Web Key (JWK) Set from the Authorization Server, which contains the cryptographic key(s) used to verify the JSON Web Signature (JWS) of the ID Token and optionally the UserInfo Response. - attribute jwk-set-uri {xsd:token}? -provider.attlist &= - ## The URI used to discover the configuration information for an OAuth 2.0 or OpenID Connect 1.0 Provider. - attribute issuer-uri {xsd:token}? - -oauth2-resource-server = - ## Configures authentication support as an OAuth 2.0 Resource Server. - element oauth2-resource-server {oauth2-resource-server.attlist, (jwt? & opaque-token?)} -oauth2-resource-server.attlist &= - ## Reference to an AuthenticationManagerResolver - attribute authentication-manager-resolver-ref {xsd:token}? -oauth2-resource-server.attlist &= - ## Reference to a BearerTokenResolver - attribute bearer-token-resolver-ref {xsd:token}? -oauth2-resource-server.attlist &= - ## Reference to a AuthenticationEntryPoint - attribute entry-point-ref {xsd:token}? - -jwt = - ## Configures JWT authentication - element jwt {jwt.attlist} -jwt.attlist &= - ## The URI to use to collect the JWK Set for verifying JWTs - attribute jwk-set-uri {xsd:token}? -jwt.attlist &= - ## Reference to a JwtDecoder - attribute decoder-ref {xsd:token}? -jwt.attlist &= - ## Reference to a Converter - attribute jwt-authentication-converter-ref {xsd:token}? - -opaque-token = - ## Configuration Opaque Token authentication - element opaque-token {opaque-token.attlist} -opaque-token.attlist &= - ## The URI to use to introspect opaque token attributes - attribute introspection-uri {xsd:token}? -opaque-token.attlist &= - ## The Client ID to use to authenticate the introspection request - attribute client-id {xsd:token}? -opaque-token.attlist &= - ## The Client secret to use to authenticate the introspection request - attribute client-secret {xsd:token}? -opaque-token.attlist &= - ## Reference to an OpaqueTokenIntrospector - attribute introspector-ref {xsd:token}? -opaque-token.attlist &= - ## Reference to an OpaqueTokenAuthenticationConverter responsible for converting successful introspection result into an Authentication. - attribute authentication-converter-ref {xsd:token}? - -saml2-login = - ## Configures authentication support for SAML 2.0 Login - element saml2-login {saml2-login.attlist} -saml2-login.attlist &= - ## Reference to the RelyingPartyRegistrationRepository - attribute relying-party-registration-repository-ref {xsd:token}? -saml2-login.attlist &= - ## Reference to the Saml2AuthenticationRequestRepository - attribute authentication-request-repository-ref {xsd:token}? -saml2-login.attlist &= - ## Reference to the Saml2AuthenticationRequestResolver - attribute authentication-request-resolver-ref {xsd:token}? -saml2-login.attlist &= - ## Reference to the AuthenticationConverter - attribute authentication-converter-ref {xsd:token}? -saml2-login.attlist &= - ## The URI where the filter processes authentication requests - attribute login-processing-url {xsd:token}? -saml2-login.attlist &= - ## The URI to send users to login - attribute login-page {xsd:token}? -saml2-login.attlist &= - ## Reference to the AuthenticationSuccessHandler - attribute authentication-success-handler-ref {xsd:token}? -saml2-login.attlist &= - ## Reference to the AuthenticationFailureHandler - attribute authentication-failure-handler-ref {xsd:token}? -saml2-login.attlist &= - ## Reference to the AuthenticationManager - attribute authentication-manager-ref {xsd:token}? - -saml2-logout = - ## Configures SAML 2.0 Single Logout support - element saml2-logout {saml2-logout.attlist} -saml2-logout.attlist &= - ## The URL by which the relying or asserting party can trigger logout - attribute logout-url {xsd:token}? -saml2-logout.attlist &= - ## The URL by which the asserting party can send a SAML 2.0 Logout Request - attribute logout-request-url {xsd:token}? -saml2-logout.attlist &= - ## The URL by which the asserting party can send a SAML 2.0 Logout Response - attribute logout-response-url {xsd:token}? -saml2-logout.attlist &= - ## Reference to the RelyingPartyRegistrationRepository - attribute relying-party-registration-repository-ref {xsd:token}? -saml2-logout.attlist &= - ## Reference to the Saml2LogoutRequestValidator - attribute logout-request-validator-ref {xsd:token}? -saml2-logout.attlist &= - ## Reference to the Saml2LogoutRequestResolver - attribute logout-request-resolver-ref {xsd:token}? -saml2-logout.attlist &= - ## Reference to the Saml2LogoutRequestRepository - attribute logout-request-repository-ref {xsd:token}? -saml2-logout.attlist &= - ## Reference to the Saml2LogoutResponseValidator - attribute logout-response-validator-ref {xsd:token}? -saml2-logout.attlist &= - ## Reference to the Saml2LogoutResponseResolver - attribute logout-response-resolver-ref {xsd:token}? - -relying-party-registrations = - ## Container element for relying party(ies) registered with a SAML 2.0 identity provider - element relying-party-registrations {relying-party-registrations.attlist, relying-party-registration+, asserting-party*} -relying-party-registrations.attlist &= - ## The identifier by which to refer to the repository in other beans - attribute id {xsd:token}? - -relying-party-registration = - ## Represents a relying party registered with a SAML 2.0 identity provider - element relying-party-registration {relying-party-registration.attlist, signing-credential*, decryption-credential*} -relying-party-registration.attlist &= - ## The ID that uniquely identifies the relying party registration. - attribute registration-id {xsd:token} -relying-party-registration.attlist &= - ## The location of the Identity Provider's metadata. - attribute metadata-location {xsd:token}? -relying-party-registration.attlist &= - ## The relying party's EntityID - attribute entity-id {xsd:token}? -relying-party-registration.attlist &= - ## The Assertion Consumer Service Location - attribute assertion-consumer-service-location {xsd:token}? -relying-party-registration.attlist &= - ## The Assertion Consumer Service Binding - attribute assertion-consumer-service-binding {xsd:token}? -relying-party-registration.attlist &= - ## A reference to the associated asserting party. - attribute asserting-party-id {xsd:token}? -relying-party-registration.attlist &= - ## The relying party SingleLogoutService Location - attribute single-logout-service-location {xsd:token}? -relying-party-registration.attlist &= - ## The relying party SingleLogoutService Response Location - attribute single-logout-service-response-location {xsd:token}? -relying-party-registration.attlist &= - ## The relying party SingleLogoutService Binding - attribute single-logout-service-binding {xsd:token}? - -signing-credential = - ## The relying party's signing credential - element signing-credential {signing-credential.attlist} -signing-credential.attlist &= - ## The private key location - attribute private-key-location {xsd:token} -signing-credential.attlist &= - ## The certificate location - attribute certificate-location {xsd:token} - -decryption-credential = - ## The relying party's decryption credential - element decryption-credential {decryption-credential.attlist} -decryption-credential.attlist &= - ## The private key location - attribute private-key-location {xsd:token} -decryption-credential.attlist &= - ## The certificate location - attribute certificate-location {xsd:token} - -asserting-party = - ## The configuration metadata of the Asserting party - element asserting-party {asserting-party.attlist, verification-credential*, encryption-credential*} -asserting-party.attlist &= - ## A unique identifier of the asserting party. - attribute asserting-party-id {xsd:token} -asserting-party.attlist &= - ## The asserting party's EntityID. - attribute entity-id {xsd:token} -asserting-party.attlist &= - ## Indicates the asserting party's preference that relying parties should sign the AuthnRequest before sending - attribute want-authn-requests-signed {xsd:token}? -asserting-party.attlist &= - ## The SingleSignOnService Location. - attribute single-sign-on-service-location {xsd:token} -asserting-party.attlist &= - ## The SingleSignOnService Binding. - attribute single-sign-on-service-binding {xsd:token}? -asserting-party.attlist &= - ## A comma separated list of org.opensaml.saml.ext.saml2alg.SigningMethod Algorithms for this asserting party, in preference order. - attribute signing-algorithms {xsd:token}? -asserting-party.attlist &= - ## The asserting party SingleLogoutService Location - attribute single-logout-service-location {xsd:token}? -asserting-party.attlist &= - ## The asserting party SingleLogoutService Response Location - attribute single-logout-service-response-location {xsd:token}? -asserting-party.attlist &= - ## The asserting party SingleLogoutService Binding - attribute single-logout-service-binding {xsd:token}? - -verification-credential = - ## The relying party's verification credential - element verification-credential {verification-credential.attlist} -verification-credential.attlist &= - ## The private key location - attribute private-key-location {xsd:token} -verification-credential.attlist &= - ## The certificate location - attribute certificate-location {xsd:token} - -encryption-credential = - ## The asserting party's encryption credential - element encryption-credential {encryption-credential.attlist} -encryption-credential.attlist &= - ## The private key location - attribute private-key-location {xsd:token} -encryption-credential.attlist &= - ## The certificate location - attribute certificate-location {xsd:token} - - -filter-chain-map = - ## Used to explicitly configure a FilterChainProxy instance with a FilterChainMap - element filter-chain-map {filter-chain-map.attlist, filter-chain+} -filter-chain-map.attlist &= - request-matcher? - -filter-chain = - ## Used within to define a specific URL pattern and the list of filters which apply to the URLs matching that pattern. When multiple filter-chain elements are assembled in a list in order to configure a FilterChainProxy, the most specific patterns must be placed at the top of the list, with most general ones at the bottom. - element filter-chain {filter-chain.attlist, empty} -filter-chain.attlist &= - (pattern | request-matcher-ref) -filter-chain.attlist &= - ## A comma separated list of bean names that implement Filter that should be processed for this FilterChain. If the value is none, then no Filters will be used for this FilterChain. - attribute filters {xsd:token} - -pattern = - ## The request URL pattern which will be mapped to the FilterChain. - attribute pattern {xsd:token} -request-matcher-ref = - ## Allows a RequestMatcher instance to be used, as an alternative to pattern-matching. - attribute request-matcher-ref {xsd:token} - -filter-security-metadata-source = - ## Used to explicitly configure a FilterSecurityMetadataSource bean for use with a FilterSecurityInterceptor. Usually only needed if you are configuring a FilterChainProxy explicitly, rather than using the element. The intercept-url elements used should only contain pattern, method and access attributes. Any others will result in a configuration error. - element filter-security-metadata-source {fsmds.attlist, intercept-url+} -fsmds.attlist &= - use-expressions? -fsmds.attlist &= - id? -fsmds.attlist &= - request-matcher? - -http-basic = - ## Adds support for basic authentication - element http-basic {http-basic.attlist, empty} - -http-basic.attlist &= - ## Sets the AuthenticationEntryPoint which is used by the BasicAuthenticationFilter. - attribute entry-point-ref {xsd:token}? -http-basic.attlist &= - ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter - attribute authentication-details-source-ref {xsd:token}? - -password-management = - ## Adds support for the password management. - element password-management {password-management.attlist, empty} - -password-management.attlist &= - ## The change password page. Defaults to "/change-password". - attribute change-password-page {xsd:string}? - -session-management = - ## Session-management related functionality is implemented by the addition of a SessionManagementFilter to the filter stack. - element session-management {session-management.attlist, concurrency-control?} - -session-management.attlist &= - ## Specifies that SessionAuthenticationStrategy must be explicitly invoked. Default false (i.e. SessionManagementFilter will implicitly invoke SessionAuthenticationStrategy). - attribute authentication-strategy-explicit-invocation {xsd:boolean}? -session-management.attlist &= - ## Indicates how session fixation protection will be applied when a user authenticates. If set to "none", no protection will be applied. "newSession" will create a new empty session, with only Spring Security-related attributes migrated. "migrateSession" will create a new session and copy all session attributes to the new session. In Servlet 3.1 (Java EE 7) and newer containers, specifying "changeSessionId" will keep the existing session and use the container-supplied session fixation protection (HttpServletRequest#changeSessionId()). Defaults to "changeSessionId" in Servlet 3.1 and newer containers, "migrateSession" in older containers. Throws an exception if "changeSessionId" is used in older containers. - attribute session-fixation-protection {"none" | "newSession" | "migrateSession" | "changeSessionId" }? -session-management.attlist &= - ## The URL to which a user will be redirected if they submit an invalid session indentifier. Typically used to detect session timeouts. - attribute invalid-session-url {xsd:token}? -session-management.attlist &= - ## Allows injection of the InvalidSessionStrategy instance used by the SessionManagementFilter - attribute invalid-session-strategy-ref {xsd:token}? -session-management.attlist &= - ## Allows injection of the SessionAuthenticationStrategy instance used by the SessionManagementFilter - attribute session-authentication-strategy-ref {xsd:token}? -session-management.attlist &= - ## Defines the URL of the error page which should be shown when the SessionAuthenticationStrategy raises an exception. If not set, an unauthorized (401) error code will be returned to the client. Note that this attribute doesn't apply if the error occurs during a form-based login, where the URL for authentication failure will take precedence. - attribute session-authentication-error-url {xsd:token}? - - -concurrency-control = - ## Enables concurrent session control, limiting the number of authenticated sessions a user may have at the same time. - element concurrency-control {concurrency-control.attlist, empty} - -concurrency-control.attlist &= - ## The maximum number of sessions a single authenticated user can have open at the same time. Defaults to "1". A negative value denotes unlimited sessions. - attribute max-sessions {xsd:token}? -concurrency-control.attlist &= - ## The URL a user will be redirected to if they attempt to use a session which has been "expired" because they have logged in again. - attribute expired-url {xsd:token}? -concurrency-control.attlist &= - ## Allows injection of the SessionInformationExpiredStrategy instance used by the ConcurrentSessionFilter - attribute expired-session-strategy-ref {xsd:token}? -concurrency-control.attlist &= - ## Specifies that an unauthorized error should be reported when a user attempts to login when they already have the maximum configured sessions open. The default behaviour is to expire the original session. If the session-authentication-error-url attribute is set on the session-management URL, the user will be redirected to this URL. - attribute error-if-maximum-exceeded {xsd:boolean}? -concurrency-control.attlist &= - ## Allows you to define an alias for the SessionRegistry bean in order to access it in your own configuration. - attribute session-registry-alias {xsd:token}? -concurrency-control.attlist &= - ## Allows you to define an external SessionRegistry bean to be used by the concurrency control setup. - attribute session-registry-ref {xsd:token}? - - -remember-me = - ## Sets up remember-me authentication. If used with the "key" attribute (or no attributes) the cookie-only implementation will be used. Specifying "token-repository-ref" or "remember-me-data-source-ref" will use the more secure, persisten token approach. - element remember-me {remember-me.attlist} -remember-me.attlist &= - ## The "key" used to identify cookies from a specific token-based remember-me application. You should set this to a unique value for your application. If unset, it will default to a random value generated by SecureRandom. - attribute key {xsd:token}? - -remember-me.attlist &= - (token-repository-ref | remember-me-data-source-ref | remember-me-services-ref) - -remember-me.attlist &= - user-service-ref? - -remember-me.attlist &= - ## Exports the internally defined RememberMeServices as a bean alias, allowing it to be used by other beans in the application context. - attribute services-alias {xsd:token}? - -remember-me.attlist &= - ## Determines whether the "secure" flag will be set on the remember-me cookie. If set to true, the cookie will only be submitted over HTTPS (recommended). By default, secure cookies will be used if the request is made on a secure connection. - attribute use-secure-cookie {xsd:boolean}? - -remember-me.attlist &= - ## The period (in seconds) for which the remember-me cookie should be valid. - attribute token-validity-seconds {xsd:string}? - -remember-me.attlist &= - ## Reference to an AuthenticationSuccessHandler bean which should be used to handle a successful remember-me authentication. - attribute authentication-success-handler-ref {xsd:token}? -remember-me.attlist &= - ## The name of the request parameter which toggles remember-me authentication. Defaults to 'remember-me'. - attribute remember-me-parameter {xsd:token}? -remember-me.attlist &= - ## The name of cookie which store the token for remember-me authentication. Defaults to 'remember-me'. - attribute remember-me-cookie {xsd:token}? - -token-repository-ref = - ## Reference to a PersistentTokenRepository bean for use with the persistent token remember-me implementation. - attribute token-repository-ref {xsd:token} -remember-me-services-ref = - ## Allows a custom implementation of RememberMeServices to be used. Note that this implementation should return RememberMeAuthenticationToken instances with the same "key" value as specified in the remember-me element. Alternatively it should register its own AuthenticationProvider. It should also implement the LogoutHandler interface, which will be invoked when a user logs out. Typically the remember-me cookie would be removed on logout. - attribute services-ref {xsd:token}? -remember-me-data-source-ref = - ## DataSource bean for the database that contains the token repository schema. - data-source-ref - -anonymous = - ## Adds support for automatically granting all anonymous web requests a particular principal identity and a corresponding granted authority. - element anonymous {anonymous.attlist} -anonymous.attlist &= - ## The key shared between the provider and filter. This generally does not need to be set. If unset, it will default to a random value generated by SecureRandom. - attribute key {xsd:token}? -anonymous.attlist &= - ## The username that should be assigned to the anonymous request. This allows the principal to be identified, which may be important for logging and auditing. if unset, defaults to "anonymousUser". - attribute username {xsd:token}? -anonymous.attlist &= - ## The granted authority that should be assigned to the anonymous request. Commonly this is used to assign the anonymous request particular roles, which can subsequently be used in authorization decisions. If unset, defaults to "ROLE_ANONYMOUS". - attribute granted-authority {xsd:token}? -anonymous.attlist &= - ## With the default namespace setup, the anonymous "authentication" facility is automatically enabled. You can disable it using this property. - attribute enabled {xsd:boolean}? - - -port-mappings = - ## Defines the list of mappings between http and https ports for use in redirects - element port-mappings {port-mappings.attlist, port-mapping+} - -port-mappings.attlist &= empty - -port-mapping = - ## Provides a method to map http ports to https ports when forcing a redirect. - element port-mapping {http-port, https-port} - -http-port = - ## The http port to use. - attribute http {xsd:token} - -https-port = - ## The https port to use. - attribute https {xsd:token} - - -x509 = - ## Adds support for X.509 client authentication. - element x509 {x509.attlist} -x509.attlist &= - ## The regular expression used to obtain the username from the certificate's subject. Defaults to matching on the common name using the pattern "CN=(.*?),". - attribute subject-principal-regex {xsd:token}? -x509.attlist &= - ## Explicitly specifies which user-service should be used to load user data for X.509 authenticated clients. If ommitted, the default user-service will be used. - user-service-ref? -x509.attlist &= - ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter - attribute authentication-details-source-ref {xsd:token}? - -jee = - ## Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication. - element jee {jee.attlist} -jee.attlist &= - ## A comma-separate list of roles to look for in the incoming HttpServletRequest. - attribute mappable-roles {xsd:token} -jee.attlist &= - ## Explicitly specifies which user-service should be used to load user data for container authenticated clients. If ommitted, the set of mappable-roles will be used to construct the authorities for the user. - user-service-ref? - -authentication-manager = - ## Registers the AuthenticationManager instance and allows its list of AuthenticationProviders to be defined. Also allows you to define an alias to allow you to reference the AuthenticationManager in your own beans. - element authentication-manager {authman.attlist & authentication-provider* & ldap-authentication-provider*} -authman.attlist &= - id? -authman.attlist &= - ## An alias you wish to use for the AuthenticationManager bean (not required it you are using a specific id) - attribute alias {xsd:token}? -authman.attlist &= - ## If set to true, the AuthenticationManger will attempt to clear any credentials data in the returned Authentication object, once the user has been authenticated. - attribute erase-credentials {xsd:boolean}? -authman.attlist &= - ## Use this ObservationRegistry to collect metrics on various parts of the filter chain - attribute observation-registry-ref {xsd:token}? - -authentication-provider = - ## Indicates that the contained user-service should be used as an authentication source. - element authentication-provider {ap.attlist & any-user-service & password-encoder?} -ap.attlist &= - ## Specifies a reference to a separately configured AuthenticationProvider instance which should be registered within the AuthenticationManager. - ref? -ap.attlist &= - ## Specifies a reference to a separately configured UserDetailsService from which to obtain authentication data. - user-service-ref? - -user-service = - ## Creates an in-memory UserDetailsService from a properties file or a list of "user" child elements. Usernames are converted to lower-case internally to allow for case-insensitive lookups, so this should not be used if case-sensitivity is required. - element user-service {id? & (properties-file | (user*))} -properties-file = - ## The location of a Properties file where each line is in the format of username=password,grantedAuthority[,grantedAuthority][,enabled|disabled] - attribute properties {xsd:token}? - -user = - ## Represents a user in the application. - element user {user.attlist, empty} -user.attlist &= - ## The username assigned to the user. - attribute name {xsd:token} -user.attlist &= - ## The password assigned to the user. This may be hashed if the corresponding authentication provider supports hashing (remember to set the "hash" attribute of the "user-service" element). This attribute be omitted in the case where the data will not be used for authentication, but only for accessing authorities. If omitted, the namespace will generate a random value, preventing its accidental use for authentication. Cannot be empty. - attribute password {xsd:string}? -user.attlist &= - ## One of more authorities granted to the user. Separate authorities with a comma (but no space). For example, "ROLE_USER,ROLE_ADMINISTRATOR" - attribute authorities {xsd:token} -user.attlist &= - ## Can be set to "true" to mark an account as locked and unusable. - attribute locked {xsd:boolean}? -user.attlist &= - ## Can be set to "true" to mark an account as disabled and unusable. - attribute disabled {xsd:boolean}? - -jdbc-user-service = - ## Causes creation of a JDBC-based UserDetailsService. - element jdbc-user-service {id? & jdbc-user-service.attlist} -jdbc-user-service.attlist &= - ## The bean ID of the DataSource which provides the required tables. - attribute data-source-ref {xsd:token} -jdbc-user-service.attlist &= - cache-ref? -jdbc-user-service.attlist &= - ## An SQL statement to query a username, password, and enabled status given a username. Default is "select username,password,enabled from users where username = ?" - attribute users-by-username-query {xsd:token}? -jdbc-user-service.attlist &= - ## An SQL statement to query for a user's granted authorities given a username. The default is "select username, authority from authorities where username = ?" - attribute authorities-by-username-query {xsd:token}? -jdbc-user-service.attlist &= - ## An SQL statement to query user's group authorities given a username. The default is "select g.id, g.group_name, ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id" - attribute group-authorities-by-username-query {xsd:token}? -jdbc-user-service.attlist &= - role-prefix? - -csrf = -## Element for configuration of the CsrfFilter for protection against CSRF. It also updates the default RequestCache to only replay "GET" requests. - element csrf {csrf-options.attlist} -csrf-options.attlist &= - ## Specifies if csrf protection should be disabled. Default false (i.e. CSRF protection is enabled). - attribute disabled {xsd:boolean}? -csrf-options.attlist &= - ## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS" - attribute request-matcher-ref { xsd:token }? -csrf-options.attlist &= - ## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository. - attribute token-repository-ref { xsd:token }? -csrf-options.attlist &= - ## The CsrfTokenRequestHandler to use. The default is CsrfTokenRequestAttributeHandler. - attribute request-handler-ref { xsd:token }? - -headers = -## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers. -element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & content-security-policy? & referrer-policy? & feature-policy? & permissions-policy? & cross-origin-opener-policy? & cross-origin-embedder-policy? & cross-origin-resource-policy? & header*)} -headers-options.attlist &= - ## Specifies if the default headers should be disabled. Default false. - attribute defaults-disabled {xsd:token}? -headers-options.attlist &= - ## Specifies if headers should be disabled. Default false. - attribute disabled {xsd:token}? -hsts = - ## Adds support for HTTP Strict Transport Security (HSTS) - element hsts {hsts-options.attlist} -hsts-options.attlist &= - ## Specifies if HTTP Strict Transport Security (HSTS) should be disabled. Default false. - attribute disabled {xsd:boolean}? -hsts-options.attlist &= - ## Specifies if subdomains should be included. Default true. - attribute include-subdomains {xsd:boolean}? -hsts-options.attlist &= - ## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year. - attribute max-age-seconds {xsd:integer}? -hsts-options.attlist &= - ## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true. - attribute request-matcher-ref { xsd:token }? -hsts-options.attlist &= - ## Specifies if preload should be included. Default false. - attribute preload {xsd:boolean}? - -cors = -## Element for configuration of CorsFilter. If no CorsFilter or CorsConfigurationSource is specified a HandlerMappingIntrospector is used as the CorsConfigurationSource -element cors { cors-options.attlist } -cors-options.attlist &= - ref? -cors-options.attlist &= - ## Specifies a bean id that is a CorsConfigurationSource used to construct the CorsFilter to use - attribute configuration-source-ref {xsd:token}? - -hpkp = - ## Adds support for HTTP Public Key Pinning (HPKP). - element hpkp {hpkp.pins,hpkp.attlist} -hpkp.pins = - ## The list with pins - element pins {hpkp.pin+} -hpkp.pin = - ## A pin is specified using the base64-encoded SPKI fingerprint as value and the cryptographic hash algorithm as attribute - element pin { - ## The cryptographic hash algorithm - attribute algorithm { xsd:string }?, - text - } -hpkp.attlist &= - ## Specifies if HTTP Public Key Pinning (HPKP) should be disabled. Default false. - attribute disabled {xsd:boolean}? -hpkp.attlist &= - ## Specifies if subdomains should be included. Default false. - attribute include-subdomains {xsd:boolean}? -hpkp.attlist &= - ## Sets the value for the max-age directive of the Public-Key-Pins header. Default 60 days. - attribute max-age-seconds {xsd:integer}? -hpkp.attlist &= - ## Specifies if the browser should only report pin validation failures. Default true. - attribute report-only {xsd:boolean}? -hpkp.attlist &= - ## Specifies the URI to which the browser should report pin validation failures. - attribute report-uri {xsd:string}? - -content-security-policy = - ## Adds support for Content Security Policy (CSP) - element content-security-policy {csp-options.attlist} -csp-options.attlist &= - ## The security policy directive(s) for the Content-Security-Policy header or if report-only is set to true, then the Content-Security-Policy-Report-Only header is used. - attribute policy-directives {xsd:token}? -csp-options.attlist &= - ## Set to true, to enable the Content-Security-Policy-Report-Only header for reporting policy violations only. Defaults to false. - attribute report-only {xsd:boolean}? - -referrer-policy = - ## Adds support for Referrer Policy - element referrer-policy {referrer-options.attlist} -referrer-options.attlist &= - ## The policies for the Referrer-Policy header. - attribute policy {"no-referrer","no-referrer-when-downgrade","same-origin","origin","strict-origin","origin-when-cross-origin","strict-origin-when-cross-origin","unsafe-url"}? - -feature-policy = - ## Adds support for Feature Policy - element feature-policy {feature-options.attlist} -feature-options.attlist &= - ## The security policy directive(s) for the Feature-Policy header. - attribute policy-directives {xsd:token}? - -permissions-policy = - ## Adds support for Permissions Policy - element permissions-policy {permissions-options.attlist} -permissions-options.attlist &= - ## The policies for the Permissions-Policy header. - attribute policy {xsd:token}? - -cache-control = - ## Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for every request - element cache-control {cache-control.attlist} -cache-control.attlist &= - ## Specifies if Cache Control should be disabled. Default false. - attribute disabled {xsd:boolean}? - -frame-options = - ## Enable basic clickjacking support for newer browsers (IE8+), will set the X-Frame-Options header. - element frame-options {frame-options.attlist,empty} -frame-options.attlist &= - ## If disabled, the X-Frame-Options header will not be included. Default false. - attribute disabled {xsd:boolean}? -frame-options.attlist &= - ## Specify the policy to use for the X-Frame-Options-Header. - attribute policy {"DENY","SAMEORIGIN","ALLOW-FROM"}? -frame-options.attlist &= - ## Specify the strategy to use when ALLOW-FROM is chosen. - attribute strategy {"static","whitelist","regexp"}? -frame-options.attlist &= - ## Specify a reference to the custom AllowFromStrategy to use when ALLOW-FROM is chosen. - ref? -frame-options.attlist &= - ## Specify a value to use for the chosen strategy. - attribute value {xsd:string}? -frame-options.attlist &= - ## Specify the request parameter to use for the origin when using a 'whitelist' or 'regexp' based strategy. Default is 'from'. - ## Deprecated ALLOW-FROM is an obsolete directive that no longer works in modern browsers. Instead use - ## Content-Security-Policy with the - ## frame-ancestors - ## directive. - attribute from-parameter {xsd:string}? - - -xss-protection = - ## Enable basic XSS browser protection, supported by newer browsers (IE8+), will set the X-XSS-Protection header. - element xss-protection {xss-protection.attlist,empty} -xss-protection.attlist &= - ## disable the X-XSS-Protection header. Default is 'false' meaning it is enabled. - attribute disabled {xsd:boolean}? -xss-protection.attlist &= - ## Specify the value for the X-Xss-Protection header. Defaults to "0". - attribute header-value {"0"|"1"|"1; mode=block"}? - -content-type-options = - ## Add a X-Content-Type-Options header to the resopnse. Value is always 'nosniff'. - element content-type-options {content-type-options.attlist, empty} -content-type-options.attlist &= - ## If disabled, the X-Content-Type-Options header will not be included. Default false. - attribute disabled {xsd:boolean}? - -cross-origin-opener-policy = - ## Adds support for Cross-Origin-Opener-Policy header - element cross-origin-opener-policy {cross-origin-opener-policy-options.attlist,empty} -cross-origin-opener-policy-options.attlist &= - ## The policies for the Cross-Origin-Opener-Policy header. - attribute policy {"unsafe-none","same-origin","same-origin-allow-popups"}? - -cross-origin-embedder-policy = - ## Adds support for Cross-Origin-Embedder-Policy header - element cross-origin-embedder-policy {cross-origin-embedder-policy-options.attlist,empty} -cross-origin-embedder-policy-options.attlist &= - ## The policies for the Cross-Origin-Embedder-Policy header. - attribute policy {"unsafe-none","require-corp"}? - -cross-origin-resource-policy = - ## Adds support for Cross-Origin-Resource-Policy header - element cross-origin-resource-policy {cross-origin-resource-policy-options.attlist,empty} -cross-origin-resource-policy-options.attlist &= - ## The policies for the Cross-Origin-Resource-Policy header. - attribute policy {"cross-origin","same-origin","same-site"}? - -header= - ## Add additional headers to the response. - element header {header.attlist} -header.attlist &= - ## The name of the header to add. - attribute name {xsd:token}? -header.attlist &= - ## The value for the header. - attribute value {xsd:token}? -header.attlist &= - ## Reference to a custom HeaderWriter implementation. - ref? - -any-user-service = user-service | jdbc-user-service | ldap-user-service - -custom-filter = - ## Used to indicate that a filter bean declaration should be incorporated into the security filter chain. - element custom-filter {custom-filter.attlist} - -custom-filter.attlist &= - ref - -custom-filter.attlist &= - (after | before | position) - -after = - ## The filter immediately after which the custom-filter should be placed in the chain. This feature will only be needed by advanced users who wish to mix their own filters into the security filter chain and have some knowledge of the standard Spring Security filters. The filter names map to specific Spring Security implementation filters. - attribute after {named-security-filter} -before = - ## The filter immediately before which the custom-filter should be placed in the chain - attribute before {named-security-filter} -position = - ## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter. - attribute position {named-security-filter} - -named-security-filter = "FIRST" | "DISABLE_ENCODE_URL_FILTER" | "FORCE_EAGER_SESSION_FILTER" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "SAML2_LOGOUT_REQUEST_FILTER" | "SAML2_LOGOUT_RESPONSE_FILTER" | "CSRF_FILTER" | "SAML2_LOGOUT_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "SAML2_AUTHENTICATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "SAML2_AUTHENTICATION_FILTER" | "FORM_LOGIN_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST" diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.3.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-6.3.xsd deleted file mode 100644 index 5d814f1713c..00000000000 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.3.xsd +++ /dev/null @@ -1,3821 +0,0 @@ - - - - - - Defines the hashing algorithm used on user passwords. Bcrypt is recommended. - - - - - - - - - - - - - Whether a string should be base64 encoded - - - - - - - - Defines the strategy use for matching incoming requests. Currently the options are 'mvc' - (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions - and 'ciRegex' for case-insensitive regular expressions. - - - - - - - - - - - - - - - - Specifies an IP port number. Used to configure an embedded LDAP server, for example. - - - - - - - - Specifies a URL. - - - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. - - - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. - - - - - - - - Defines a reference to a Spring bean Id. - - - - - - - - Defines a reference to a cache for use with a UserDetailsService. - - - - - - - - A reference to a user-service (or UserDetailsService bean) Id - - - - - - - - A reference to an AuthenticationManager bean - - - - - - - - A reference to a DataSource bean - - - - - - - Enables Spring Security debugging infrastructure. This will provide human-readable - (multi-line) debugging information to monitor requests coming into the security filters. - This may include sensitive information, such as request parameters or headers, and should - only be used in a development environment. - - - - - - - - - Defines a reference to a Spring bean Id. - - - - - - Defines the hashing algorithm used on user passwords. Bcrypt is recommended. - - - - - - - - - - - - - A non-empty string prefix that will be added to role strings loaded from persistent - storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is - non-empty. - - - - - - - - Enables the use of expressions in the 'access' attributes in <intercept-url> elements - rather than the traditional list of configuration attributes. Defaults to 'true'. If - enabled, each attribute should contain a single boolean expression. If the expression - evaluates to 'true', access will be granted. - - - - - - - Defines an LDAP server location or starts an embedded server. The url indicates the - location of a remote server. If no url is given, an embedded server will be started, - listening on the supplied port number. The port is optional and defaults to 33389. A - Spring LDAP ContextSource bean will be registered for the server with the id supplied. - - - - - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. - - - - - - Specifies a URL. - - - - - - Specifies an IP port number. Used to configure an embedded LDAP server, for example. - - - - - - Username (DN) of the "manager" user identity which will be used to authenticate to a - (non-embedded) LDAP server. If omitted, anonymous access will be used. - - - - - - The password for the manager DN. This is required if the manager-dn is specified. - - - - - - Explicitly specifies an ldif file resource to load into an embedded LDAP server. The - default is classpath*:*.ldiff - - - - - - Optional root suffix for the embedded LDAP server. Default is "dc=springframework,dc=org" - - - - - - Explicitly specifies which embedded ldap server should use. Values are 'apacheds' and - 'unboundid'. By default, it will depends if the library is available in the classpath. - - - - - - - - - - - - - - The optional server to use. If omitted, and a default LDAP server is registered (using - <ldap-server> with no Id), that server will be used. - - - - - - - - Group search filter. Defaults to (uniqueMember={0}). The substituted parameter is the DN - of the user. - - - - - - - - Search base for group membership searches. Defaults to "" (searching from the root). - - - - - - - - The LDAP filter used to search for users (optional). For example "(uid={0})". The - substituted parameter is the user's login name. - - - - - - - - Search base for user searches. Defaults to "". Only used with a 'user-search-filter'. - - - - - - - - The LDAP attribute name which contains the role name which will be used within Spring - Security. Defaults to "cn". - - - - - - - - Allows the objectClass of the user entry to be specified. If set, the framework will - attempt to load standard attributes for the defined class into the returned UserDetails - object - - - - - - - - - - - - - - Allows explicit customization of the loaded user object by specifying a - UserDetailsContextMapper bean which will be called with the context information from the - user's directory entry - - - - - - - This element configures a LdapUserDetailsService which is a combination of a - FilterBasedLdapUserSearch and a DefaultLdapAuthoritiesPopulator. - - - - - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. - - - - - - The optional server to use. If omitted, and a default LDAP server is registered (using - <ldap-server> with no Id), that server will be used. - - - - - - The LDAP filter used to search for users (optional). For example "(uid={0})". The - substituted parameter is the user's login name. - - - - - - Search base for user searches. Defaults to "". Only used with a 'user-search-filter'. - - - - - - Group search filter. Defaults to (uniqueMember={0}). The substituted parameter is the DN - of the user. - - - - - - Search base for group membership searches. Defaults to "" (searching from the root). - - - - - - The LDAP attribute name which contains the role name which will be used within Spring - Security. Defaults to "cn". - - - - - - Defines a reference to a cache for use with a UserDetailsService. - - - - - - A non-empty string prefix that will be added to role strings loaded from persistent - storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is - non-empty. - - - - - - Allows the objectClass of the user entry to be specified. If set, the framework will - attempt to load standard attributes for the defined class into the returned UserDetails - object - - - - - - - - - - - - Allows explicit customization of the loaded user object by specifying a - UserDetailsContextMapper bean which will be called with the context information from the - user's directory entry - - - - - - - - - The optional server to use. If omitted, and a default LDAP server is registered (using - <ldap-server> with no Id), that server will be used. - - - - - - Search base for user searches. Defaults to "". Only used with a 'user-search-filter'. - - - - - - The LDAP filter used to search for users (optional). For example "(uid={0})". The - substituted parameter is the user's login name. - - - - - - Search base for group membership searches. Defaults to "" (searching from the root). - - - - - - Group search filter. Defaults to (uniqueMember={0}). The substituted parameter is the DN - of the user. - - - - - - The LDAP attribute name which contains the role name which will be used within Spring - Security. Defaults to "cn". - - - - - - A specific pattern used to build the user's DN, for example "uid={0},ou=people". The key - "{0}" must be present and will be substituted with the username. - - - - - - A non-empty string prefix that will be added to role strings loaded from persistent - storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is - non-empty. - - - - - - Allows the objectClass of the user entry to be specified. If set, the framework will - attempt to load standard attributes for the defined class into the returned UserDetails - object - - - - - - - - - - - - Allows explicit customization of the loaded user object by specifying a - UserDetailsContextMapper bean which will be called with the context information from the - user's directory entry - - - - - - - - - The attribute in the directory which contains the user password. Defaults to - "userPassword". - - - - - - Defines the hashing algorithm used on user passwords. Bcrypt is recommended. - - - - - - - - - - - - Can be used inside a bean definition to add a security interceptor to the bean and set up - access configuration attributes for the bean's methods - - - - - - - Defines a protected method and the access control configuration attributes that apply to - it. We strongly advise you NOT to mix "protect" declarations with any services provided - "global-method-security". - - - - - - - - - - - - - - Optional AccessDecisionManager bean ID to be used by the created method security - interceptor. - - - - - - Use the AuthorizationManager API instead of AccessDecisionManager (defaults to true) - - - - - - Use this AuthorizationManager instead of the default (supercedes - use-authorization-manager) - - - - - - - - - A method name - - - - - - Access configuration attributes list that applies to the method, e.g. "ROLE_A,ROLE_B". - - - - - - - Creates a MethodSecurityMetadataSource instance - - - - - - - Defines a protected method and the access control configuration attributes that apply to - it. We strongly advise you NOT to mix "protect" declarations with any services provided - "global-method-security". - - - - - - - - - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. - - - - - - Enables the use of expressions in the 'access' attributes in <intercept-url> elements - rather than the traditional list of configuration attributes. Defaults to 'true'. If - enabled, each attribute should contain a single boolean expression. If the expression - evaluates to 'true', access will be granted. - - - - - - - Provides method security for all beans registered in the Spring application context. - Specifically, beans will be scanned for matches with Spring Security annotations. Where - there is a match, the beans will automatically be proxied and security authorization - applied to the methods accordingly. Interceptors are invoked in the order specified in - AuthorizationInterceptorsOrder. Use can create your own interceptors using Spring AOP. - Also, annotation-based interception can be overridden by expressions listed in - <protect-pointcut> elements. - - - - - - - Defines the SecurityExpressionHandler instance which will be used if expression-based - access-control is enabled. A default implementation (with no ACL support) will be used if - not supplied. - - - - - - - - - Defines a protected pointcut and the access control configuration attributes that apply to - it. Every bean registered in the Spring application context that provides a method that - matches the pointcut will receive security authorization. - - - - - - - - - - - - - - Specifies whether the use of Spring Security's pre and post invocation annotations - (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this - application context. Defaults to "true". - - - - - - Specifies whether the use of Spring Security's @Secured annotations should be enabled for - this application context. Defaults to "false". - - - - - - Specifies whether JSR-250 style attributes are to be used (for example "RolesAllowed"). - This will require the javax.annotation.security classes on the classpath. Defaults to - "false". - - - - - - If true, class-based proxying will be used instead of interface-based proxying. - - - - - - If set to aspectj, then use AspectJ to intercept method invocation - - - - - - - - - - - Specifies the security context holder strategy to use, by default uses a ThreadLocal-based - strategy - - - - - - Use this ObservationRegistry to collect metrics on various parts of the filter chain - - - - - - - Provides method security for all beans registered in the Spring application context. - Specifically, beans will be scanned for matches with the ordered list of - "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a - match, the beans will automatically be proxied and security authorization applied to the - methods accordingly. If you use and enable all four sources of method security metadata - (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250 - security annotations), the metadata sources will be queried in that order. In practical - terms, this enables you to use XML to override method security metadata expressed in - annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize - etc.), @Secured and finally JSR-250. - - - - - - - - Allows the default expression-based mechanism for handling Spring Security's pre and post - invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) to be - replace entirely. Only applies if these annotations are enabled. - - - - - - - Defines the PrePostInvocationAttributeFactory instance which is used to generate pre and - post invocation metadata from the annotated methods. - - - - - - - - - Customizes the PreInvocationAuthorizationAdviceVoter with the ref as the - PreInvocationAuthorizationAdviceVoter for the <pre-post-annotation-handling> element. - - - - - - - - - Customizes the PostInvocationAdviceProvider with the ref as the - PostInvocationAuthorizationAdvice for the <pre-post-annotation-handling> element. - - - - - - - - - - - - Defines the SecurityExpressionHandler instance which will be used if expression-based - access-control is enabled. A default implementation (with no ACL support) will be used if - not supplied. - - - - - - - - - - Defines a protected pointcut and the access control configuration attributes that apply to - it. Every bean registered in the Spring application context that provides a method that - matches the pointcut will receive security authorization. - - - - - - - - - Allows addition of extra AfterInvocationProvider beans which should be called by the - MethodSecurityInterceptor created by global-method-security. - - - - - - - - - - - - - - Specifies whether the use of Spring Security's pre and post invocation annotations - (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this - application context. Defaults to "disabled". - - - - - - - - - - - - Specifies whether the use of Spring Security's @Secured annotations should be enabled for - this application context. Defaults to "disabled". - - - - - - - - - - - - Specifies whether JSR-250 style attributes are to be used (for example "RolesAllowed"). - This will require the javax.annotation.security classes on the classpath. Defaults to - "disabled". - - - - - - - - - - - - Optional AccessDecisionManager bean ID to override the default used for method security. - - - - - - Optional RunAsmanager implementation which will be used by the configured - MethodSecurityInterceptor - - - - - - Allows the advice "order" to be set for the method security interceptor. - - - - - - If true, class based proxying will be used instead of interface based proxying. - - - - - - Can be used to specify that AspectJ should be used instead of the default Spring AOP. If - set, secured classes must be woven with the AnnotationSecurityAspect from the - spring-security-aspects module. - - - - - - - - - - - An external MethodSecurityMetadataSource instance can be supplied which will take priority - over other sources (such as the default annotations). - - - - - - A reference to an AuthenticationManager bean - - - - - - - - - - - - - - - An AspectJ expression, including the 'execution' keyword. For example, 'execution(int - com.foo.TargetObject.countLength(String))' (without the quotes). - - - - - - Access configuration attributes list that applies to all methods matching the pointcut, - e.g. "ROLE_A,ROLE_B" - - - - - - - Allows securing a Message Broker. There are two modes. If no id is specified: ensures that - any SimpAnnotationMethodMessageHandler has the AuthenticationPrincipalArgumentResolver - registered as a custom argument resolver; ensures that the - SecurityContextChannelInterceptor is automatically registered for the - clientInboundChannel; and that a ChannelSecurityInterceptor is registered with the - clientInboundChannel. If the id is specified, creates a ChannelSecurityInterceptor that - can be manually registered with the clientInboundChannel. - - - - - - - - Defines the SecurityExpressionHandler instance which will be used if expression-based - access-control is enabled. A default implementation (with no ACL support) will be used if - not supplied. - - - - - - - - - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. If specified, - explicit configuration within clientInboundChannel is required. If not specified, ensures - that any SimpAnnotationMethodMessageHandler has the - AuthenticationPrincipalArgumentResolver registered as a custom argument resolver; ensures - that the SecurityContextChannelInterceptor is automatically registered for the - clientInboundChannel; and that a ChannelSecurityInterceptor is registered with the - clientInboundChannel. - - - - - - Disables the requirement for CSRF token to be present in the Stomp headers (default - false). Changing the default is useful if it is necessary to allow other origins to make - SockJS connections. - - - - - - Use this AuthorizationManager instead of deriving one from <intercept-message> elements - - - - - - Use AuthorizationManager API instead of SecurityMetadatasource (defaults to true) - - - - - - Use this SecurityContextHolderStrategy (note only supported in conjunction with the - AuthorizationManager API) - - - - - - - Creates an authorization rule for a websocket message. - - - - - - - - - - The destination ant pattern which will be mapped to the access attribute. For example, /** - matches any message with a destination, /admin/** matches any message that has a - destination that starts with admin. - - - - - - The access configuration attributes that apply for the configured message. For example, - permitAll grants access to anyone, hasRole('ROLE_ADMIN') requires the user have the role - 'ROLE_ADMIN'. - - - - - - The type of message to match on. Valid values are defined in SimpMessageType (i.e. - CONNECT, CONNECT_ACK, HEARTBEAT, MESSAGE, SUBSCRIBE, UNSUBSCRIBE, DISCONNECT, - DISCONNECT_ACK, OTHER). - - - - - - - - - - - - - - - - - - - - Allows a custom instance of HttpFirewall to be injected into the FilterChainProxy created - by the namespace. - - - - - - - - - Container element for HTTP security configuration. Multiple elements can now be defined, - each with a specific pattern to which the enclosed security configuration applies. A - pattern can also be configured to bypass Spring Security's filters completely by setting - the "security" attribute to "none". - - - - - - - Specifies the access attributes and/or filter list for a particular set of URLs. - - - - - - - - - Defines the access-denied strategy that should be used. An access denied page can be - defined or a reference to an AccessDeniedHandler instance. - - - - - - - - - Sets up a form login configuration for authentication with a username and password - - - - - - - - - - - - Configures authentication support for SAML 2.0 Login - - - - - - - - - Configures SAML 2.0 Single Logout support - - - - - - - - - Adds support for X.509 client authentication. - - - - - - - - - - Adds support for basic authentication - - - - - - - - - Incorporates a logout processing filter. Most web applications require a logout filter, - although you may not require one if you write a controller to provider similar logic. - - - - - - - - - - Session-management related functionality is implemented by the addition of a - SessionManagementFilter to the filter stack. - - - - - - - Enables concurrent session control, limiting the number of authenticated sessions a user - may have at the same time. - - - - - - - - - - - - - Sets up remember-me authentication. If used with the "key" attribute (or no attributes) - the cookie-only implementation will be used. Specifying "token-repository-ref" or - "remember-me-data-source-ref" will use the more secure, persisten token approach. - - - - - - - - - Adds support for automatically granting all anonymous web requests a particular principal - identity and a corresponding granted authority. - - - - - - - - - Defines the list of mappings between http and https ports for use in redirects - - - - - - - Provides a method to map http ports to https ports when forcing a redirect. - - - - - - - - - - - - - - - Defines the SecurityExpressionHandler instance which will be used if expression-based - access-control is enabled. A default implementation (with no ACL support) will be used if - not supplied. - - - - - - - - - - - - - - - - - The request URL pattern which will be mapped to the filter chain created by this <http> - element. If omitted, the filter chain will match all requests. - - - - - - When set to 'none', requests matching the pattern attribute will be ignored by Spring - Security. No security filters will be applied and no SecurityContext will be available. If - set, the <http> element must be empty, with no children. - - - - - - - - - - - Allows a RequestMatcher instance to be used, as an alternative to pattern-matching. - - - - - - A legacy attribute which automatically registers a login form, BASIC authentication and a - logout URL and logout services. If unspecified, defaults to "false". We'd recommend you - avoid using this and instead explicitly configure the services you require. - - - - - - Enables the use of expressions in the 'access' attributes in <intercept-url> elements - rather than the traditional list of configuration attributes. Defaults to 'true'. If - enabled, each attribute should contain a single boolean expression. If the expression - evaluates to 'true', access will be granted. - - - - - - A reference to a SecurityContextHolderStrategy bean. This can be used to customize how the - SecurityContextHolder is stored during a request - - - - - - Controls the eagerness with which an HTTP session is created by Spring Security classes. - If not set, defaults to "ifRequired". If "stateless" is used, this implies that the - application guarantees that it will not create a session. This differs from the use of - "never" which means that Spring Security will not create a session, but will make use of - one if the application does. - - - - - - - - - - - - - - A reference to a SecurityContextRepository bean. This can be used to customize how the - SecurityContext is stored between requests. - - - - - - Optional attribute that specifies that the SecurityContext should require explicit saving - rather than being synchronized from the SecurityContextHolder. Defaults to "true". - - - - - - Defines the strategy use for matching incoming requests. Currently the options are 'mvc' - (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions - and 'ciRegex' for case-insensitive regular expressions. - - - - - - - - - - - - - - Provides versions of HttpServletRequest security methods such as isUserInRole() and - getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to - "true". - - - - - - If available, runs the request as the Subject acquired from the JaasAuthenticationToken. - Defaults to "false". - - - - - - Use AuthorizationManager API instead of SecurityMetadataSource (defaults to true) - - - - - - Use this AuthorizationManager instead of deriving one from <intercept-url> elements - - - - - - Optional attribute specifying the ID of the AccessDecisionManager implementation which - should be used for authorizing HTTP requests. - - - - - - Optional attribute specifying the realm name that will be used for all authentication - features that require a realm name (eg BASIC and Digest authentication). If unspecified, - defaults to "Spring Security Application". - - - - - - Allows a customized AuthenticationEntryPoint to be set on the ExceptionTranslationFilter. - - - - - - Corresponds to the observeOncePerRequest property of FilterSecurityInterceptor. Defaults - to "false" - - - - - - Corresponds to the shouldFilterAllDispatcherTypes property of AuthorizationFilter. Do not - work when use-authorization-manager=false. Defaults to "true". - - - - - - Prevents the jsessionid parameter from being added to rendered URLs. Defaults to "true" - (rewriting is disabled). - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. - - - - - - A reference to an AuthenticationManager bean - - - - - - Use this ObservationRegistry to collect metrics on various parts of the filter chain - - - - - - - - - Defines a reference to a Spring bean Id. - - - - - - The access denied page that an authenticated user will be redirected to if they request a - page which they don't have the authority to access. - - - - - - - - The access denied page that an authenticated user will be redirected to if they request a - page which they don't have the authority to access. - - - - - - - - - The request URL pattern which will be mapped to the FilterChain. - - - - - - Allows a RequestMatcher instance to be used, as an alternative to pattern-matching. - - - - - - The access configuration attributes that apply for the configured path. - - - - - - The HTTP Method for which the access configuration attributes should apply. If not - specified, the attributes will apply to any method. - - - - - - - - - - - - - - - - - - Used to specify that a URL must be accessed over http or https, or that there is no - preference. The value should be "http", "https" or "any", respectively. - - - - - - The path to the servlet. This attribute is only applicable when 'request-matcher' is - 'mvc'. In addition, the value is only required in the following 2 use cases: 1) There are - 2 or more HttpServlet's registered in the ServletContext that have mappings starting with - '/' and are different; 2) The pattern starts with the same value of a registered - HttpServlet path, excluding the default (root) HttpServlet '/'. - - - - - - - - - Specifies the URL that will cause a logout. Spring Security will initialize a filter that - responds to this particular URL. Defaults to /logout if unspecified. - - - - - - Specifies the URL to display once the user has logged out. If not specified, defaults to - <form-login-login-page>/?logout (i.e. /login?logout). - - - - - - Specifies whether a logout also causes HttpSession invalidation, which is generally - desirable. If unspecified, defaults to true. - - - - - - A reference to a LogoutSuccessHandler implementation which will be used to determine the - destination to which the user is taken after logging out. - - - - - - A comma-separated list of the names of cookies which should be deleted when the user logs - out - - - - - - - Allow the RequestCache used for saving requests during the login process to be set - - - - - - - - - - - The URL that the login form is posted to. If unspecified, it defaults to /login. - - - - - - The name of the request parameter which contains the username. Defaults to 'username'. - - - - - - The name of the request parameter which contains the password. Defaults to 'password'. - - - - - - The URL that will be redirected to after successful authentication, if the user's previous - action could not be resumed. This generally happens if the user visits a login page - without having first requested a secured operation that triggers authentication. If - unspecified, defaults to the root of the application. - - - - - - Whether the user should always be redirected to the default-target-url after login. - - - - - - The URL for the login page. If no login URL is specified, Spring Security will - automatically create a login URL at GET /login and a corresponding filter to render that - login URL when requested. - - - - - - The URL for the login failure page. If no login failure URL is specified, Spring Security - will automatically create a failure login URL at /login?error and a corresponding filter - to render that login failure URL when requested. - - - - - - Reference to an AuthenticationSuccessHandler bean which should be used to handle a - successful authentication request. Should not be used in combination with - default-target-url (or always-use-default-target-url) as the implementation should always - deal with navigation to the subsequent destination - - - - - - Reference to an AuthenticationFailureHandler bean which should be used to handle a failed - authentication request. Should not be used in combination with authentication-failure-url - as the implementation should always deal with navigation to the subsequent destination - - - - - - Reference to an AuthenticationDetailsSource which will be used by the authentication - filter - - - - - - The URL for the ForwardAuthenticationFailureHandler - - - - - - The URL for the ForwardAuthenticationSuccessHandler - - - - - - - Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider. - - - - - - - - - - Reference to the ClientRegistrationRepository - - - - - - Reference to the OAuth2AuthorizedClientRepository - - - - - - Reference to the OAuth2AuthorizedClientService - - - - - - Reference to the AuthorizationRequestRepository - - - - - - Reference to the OAuth2AuthorizationRequestResolver - - - - - - Reference to the authorization RedirectStrategy - - - - - - Reference to the OAuth2AccessTokenResponseClient - - - - - - Reference to the GrantedAuthoritiesMapper - - - - - - Reference to the OAuth2UserService - - - - - - Reference to the OpenID Connect OAuth2UserService - - - - - - The URI where the filter processes authentication requests - - - - - - The URI to send users to login - - - - - - Reference to the AuthenticationSuccessHandler - - - - - - Reference to the AuthenticationFailureHandler - - - - - - Reference to the JwtDecoderFactory used by OidcAuthorizationCodeAuthenticationProvider - - - - - - - Configures OAuth 2.0 Client support. - - - - - - - - - - - - - Reference to the ClientRegistrationRepository - - - - - - Reference to the OAuth2AuthorizedClientRepository - - - - - - Reference to the OAuth2AuthorizedClientService - - - - - - - Configures OAuth 2.0 Authorization Code Grant. - - - - - - - - - - Reference to the AuthorizationRequestRepository - - - - - - Reference to the authorization RedirectStrategy - - - - - - Reference to the OAuth2AuthorizationRequestResolver - - - - - - Reference to the OAuth2AccessTokenResponseClient - - - - - - - Container element for client(s) registered with an OAuth 2.0 or OpenID Connect 1.0 - Provider. - - - - - - - - - - - - Represents a client registered with an OAuth 2.0 or OpenID Connect 1.0 Provider. - - - - - - - - - - The ID that uniquely identifies the client registration. - - - - - - The client identifier. - - - - - - The client secret. - - - - - - The method used to authenticate the client with the provider. The supported values are - client_secret_basic, client_secret_post and none (public clients). - - - - - - - - - - - - - - - The OAuth 2.0 Authorization Framework defines four Authorization Grant types. The - supported values are authorization_code, client_credentials and password. - - - - - - - - - - - - - The client’s registered redirect URI that the Authorization Server redirects the - end-user’s user-agent to after the end-user has authenticated and authorized access to the - client. - - - - - - A comma-separated list of scope(s) requested by the client during the Authorization - Request flow, such as openid, email, or profile. - - - - - - A descriptive name used for the client. The name may be used in certain scenarios, such as - when displaying the name of the client in the auto-generated login page. - - - - - - A reference to the associated provider. May reference a 'provider' element or use one of - the common providers (google, github, facebook, okta). - - - - - - - The configuration information for an OAuth 2.0 or OpenID Connect 1.0 Provider. - - - - - - - - - - The ID that uniquely identifies the provider. - - - - - - The Authorization Endpoint URI for the Authorization Server. - - - - - - The Token Endpoint URI for the Authorization Server. - - - - - - The UserInfo Endpoint URI used to access the claims/attributes of the authenticated - end-user. - - - - - - The authentication method used when sending the access token to the UserInfo Endpoint. The - supported values are header, form and query. - - - - - - - - - - - - - The name of the attribute returned in the UserInfo Response that references the Name or - Identifier of the end-user. - - - - - - The URI used to retrieve the JSON Web Key (JWK) Set from the Authorization Server, which - contains the cryptographic key(s) used to verify the JSON Web Signature (JWS) of the ID - Token and optionally the UserInfo Response. - - - - - - The URI used to discover the configuration information for an OAuth 2.0 or OpenID Connect - 1.0 Provider. - - - - - - - Configures authentication support as an OAuth 2.0 Resource Server. - - - - - - - - - - - - - - Reference to an AuthenticationManagerResolver - - - - - - Reference to a BearerTokenResolver - - - - - - Reference to a AuthenticationEntryPoint - - - - - - - Configures JWT authentication - - - - - - - - - - The URI to use to collect the JWK Set for verifying JWTs - - - - - - Reference to a JwtDecoder - - - - - - Reference to a Converter<Jwt, AbstractAuthenticationToken> - - - - - - - Configuration Opaque Token authentication - - - - - - - - - - The URI to use to introspect opaque token attributes - - - - - - The Client ID to use to authenticate the introspection request - - - - - - The Client secret to use to authenticate the introspection request - - - - - - Reference to an OpaqueTokenIntrospector - - - - - - Reference to an OpaqueTokenAuthenticationConverter responsible for converting successful - introspection result into an Authentication. - - - - - - - - - Reference to the RelyingPartyRegistrationRepository - - - - - - Reference to the Saml2AuthenticationRequestRepository - - - - - - Reference to the Saml2AuthenticationRequestResolver - - - - - - Reference to the AuthenticationConverter - - - - - - The URI where the filter processes authentication requests - - - - - - The URI to send users to login - - - - - - Reference to the AuthenticationSuccessHandler - - - - - - Reference to the AuthenticationFailureHandler - - - - - - Reference to the AuthenticationManager - - - - - - - - - The URL by which the relying or asserting party can trigger logout - - - - - - The URL by which the asserting party can send a SAML 2.0 Logout Request - - - - - - The URL by which the asserting party can send a SAML 2.0 Logout Response - - - - - - Reference to the RelyingPartyRegistrationRepository - - - - - - Reference to the Saml2LogoutRequestValidator - - - - - - Reference to the Saml2LogoutRequestResolver - - - - - - Reference to the Saml2LogoutRequestRepository - - - - - - Reference to the Saml2LogoutResponseValidator - - - - - - Reference to the Saml2LogoutResponseResolver - - - - - - - Container element for relying party(ies) registered with a SAML 2.0 identity provider - - - - - - - - - - - - - - The identifier by which to refer to the repository in other beans - - - - - - - Represents a relying party registered with a SAML 2.0 identity provider - - - - - - - - - - - - - - The ID that uniquely identifies the relying party registration. - - - - - - The location of the Identity Provider's metadata. - - - - - - The relying party's EntityID - - - - - - The Assertion Consumer Service Location - - - - - - The Assertion Consumer Service Binding - - - - - - A reference to the associated asserting party. - - - - - - The relying party <a - href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService - Location</a> - - - - - - The relying party <a - href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService - Response Location</a> - - - - - - The relying party <a - href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService - Binding</a> - - - - - - - The relying party's signing credential - - - - - - - - - - The private key location - - - - - - The certificate location - - - - - - - The relying party's decryption credential - - - - - - - - - - The private key location - - - - - - The certificate location - - - - - - - The configuration metadata of the Asserting party - - - - - - - - - - - - - - A unique identifier of the asserting party. - - - - - - The asserting party's EntityID. - - - - - - Indicates the asserting party's preference that relying parties should sign the - AuthnRequest before sending - - - - - - The <a - href="https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.5%20Endpoint">SingleSignOnService</a> - Location. - - - - - - The <a - href="https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.5%20Endpoint">SingleSignOnService</a> - Binding. - - - - - - A comma separated list of org.opensaml.saml.ext.saml2alg.SigningMethod Algorithms for this - asserting party, in preference order. - - - - - - The asserting party <a - href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService - Location</a> - - - - - - The asserting party <a - href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService - Response Location</a> - - - - - - The asserting party <a - href="https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService - Binding</a> - - - - - - - The relying party's verification credential - - - - - - - - - - The private key location - - - - - - The certificate location - - - - - - - The asserting party's encryption credential - - - - - - - - - - The private key location - - - - - - The certificate location - - - - - - - Used to explicitly configure a FilterChainProxy instance with a FilterChainMap - - - - - - - - - - - - - Defines the strategy use for matching incoming requests. Currently the options are 'mvc' - (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions - and 'ciRegex' for case-insensitive regular expressions. - - - - - - - - - - - - - - - Used within to define a specific URL pattern and the list of filters which apply to the - URLs matching that pattern. When multiple filter-chain elements are assembled in a list in - order to configure a FilterChainProxy, the most specific patterns must be placed at the - top of the list, with most general ones at the bottom. - - - - - - - - - - The request URL pattern which will be mapped to the FilterChain. - - - - - - Allows a RequestMatcher instance to be used, as an alternative to pattern-matching. - - - - - - A comma separated list of bean names that implement Filter that should be processed for - this FilterChain. If the value is none, then no Filters will be used for this FilterChain. - - - - - - - - The request URL pattern which will be mapped to the FilterChain. - - - - - - - - Allows a RequestMatcher instance to be used, as an alternative to pattern-matching. - - - - - - - Used to explicitly configure a FilterSecurityMetadataSource bean for use with a - FilterSecurityInterceptor. Usually only needed if you are configuring a FilterChainProxy - explicitly, rather than using the <http> element. The intercept-url elements used should - only contain pattern, method and access attributes. Any others will result in a - configuration error. - - - - - - - Specifies the access attributes and/or filter list for a particular set of URLs. - - - - - - - - - - - - - - Enables the use of expressions in the 'access' attributes in <intercept-url> elements - rather than the traditional list of configuration attributes. Defaults to 'true'. If - enabled, each attribute should contain a single boolean expression. If the expression - evaluates to 'true', access will be granted. - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. - - - - - - Defines the strategy use for matching incoming requests. Currently the options are 'mvc' - (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions - and 'ciRegex' for case-insensitive regular expressions. - - - - - - - - - - - - - - - - - Sets the AuthenticationEntryPoint which is used by the BasicAuthenticationFilter. - - - - - - Reference to an AuthenticationDetailsSource which will be used by the authentication - filter - - - - - - - Adds support for the password management. - - - - - - - - - - The change password page. Defaults to "/change-password". - - - - - - - - - Specifies that SessionAuthenticationStrategy must be explicitly invoked. Default false - (i.e. SessionManagementFilter will implicitly invoke SessionAuthenticationStrategy). - - - - - - Indicates how session fixation protection will be applied when a user authenticates. If - set to "none", no protection will be applied. "newSession" will create a new empty - session, with only Spring Security-related attributes migrated. "migrateSession" will - create a new session and copy all session attributes to the new session. In Servlet 3.1 - (Java EE 7) and newer containers, specifying "changeSessionId" will keep the existing - session and use the container-supplied session fixation protection - (HttpServletRequest#changeSessionId()). Defaults to "changeSessionId" in Servlet 3.1 and - newer containers, "migrateSession" in older containers. Throws an exception if - "changeSessionId" is used in older containers. - - - - - - - - - - - - - - The URL to which a user will be redirected if they submit an invalid session indentifier. - Typically used to detect session timeouts. - - - - - - Allows injection of the InvalidSessionStrategy instance used by the - SessionManagementFilter - - - - - - Allows injection of the SessionAuthenticationStrategy instance used by the - SessionManagementFilter - - - - - - Defines the URL of the error page which should be shown when the - SessionAuthenticationStrategy raises an exception. If not set, an unauthorized (401) error - code will be returned to the client. Note that this attribute doesn't apply if the error - occurs during a form-based login, where the URL for authentication failure will take - precedence. - - - - - - - - - The maximum number of sessions a single authenticated user can have open at the same time. - Defaults to "1". A negative value denotes unlimited sessions. - - - - - - The URL a user will be redirected to if they attempt to use a session which has been - "expired" because they have logged in again. - - - - - - Allows injection of the SessionInformationExpiredStrategy instance used by the - ConcurrentSessionFilter - - - - - - Specifies that an unauthorized error should be reported when a user attempts to login when - they already have the maximum configured sessions open. The default behaviour is to expire - the original session. If the session-authentication-error-url attribute is set on the - session-management URL, the user will be redirected to this URL. - - - - - - Allows you to define an alias for the SessionRegistry bean in order to access it in your - own configuration. - - - - - - Allows you to define an external SessionRegistry bean to be used by the concurrency - control setup. - - - - - - - - - The "key" used to identify cookies from a specific token-based remember-me application. - You should set this to a unique value for your application. If unset, it will default to a - random value generated by SecureRandom. - - - - - - Reference to a PersistentTokenRepository bean for use with the persistent token - remember-me implementation. - - - - - - A reference to a DataSource bean - - - - - - - A reference to a user-service (or UserDetailsService bean) Id - - - - - - Exports the internally defined RememberMeServices as a bean alias, allowing it to be used - by other beans in the application context. - - - - - - Determines whether the "secure" flag will be set on the remember-me cookie. If set to - true, the cookie will only be submitted over HTTPS (recommended). By default, secure - cookies will be used if the request is made on a secure connection. - - - - - - The period (in seconds) for which the remember-me cookie should be valid. - - - - - - Reference to an AuthenticationSuccessHandler bean which should be used to handle a - successful remember-me authentication. - - - - - - The name of the request parameter which toggles remember-me authentication. Defaults to - 'remember-me'. - - - - - - The name of cookie which store the token for remember-me authentication. Defaults to - 'remember-me'. - - - - - - - - Reference to a PersistentTokenRepository bean for use with the persistent token - remember-me implementation. - - - - - - - - Allows a custom implementation of RememberMeServices to be used. Note that this - implementation should return RememberMeAuthenticationToken instances with the same "key" - value as specified in the remember-me element. Alternatively it should register its own - AuthenticationProvider. It should also implement the LogoutHandler interface, which will - be invoked when a user logs out. Typically the remember-me cookie would be removed on - logout. - - - - - - - - - - - - The key shared between the provider and filter. This generally does not need to be set. If - unset, it will default to a random value generated by SecureRandom. - - - - - - The username that should be assigned to the anonymous request. This allows the principal - to be identified, which may be important for logging and auditing. if unset, defaults to - "anonymousUser". - - - - - - The granted authority that should be assigned to the anonymous request. Commonly this is - used to assign the anonymous request particular roles, which can subsequently be used in - authorization decisions. If unset, defaults to "ROLE_ANONYMOUS". - - - - - - With the default namespace setup, the anonymous "authentication" facility is automatically - enabled. You can disable it using this property. - - - - - - - - - - The http port to use. - - - - - - - - The https port to use. - - - - - - - - - The regular expression used to obtain the username from the certificate's subject. - Defaults to matching on the common name using the pattern "CN=(.*?),". - - - - - - A reference to a user-service (or UserDetailsService bean) Id - - - - - - Reference to an AuthenticationDetailsSource which will be used by the authentication - filter - - - - - - - Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration - with container authentication. - - - - - - - - - - A comma-separate list of roles to look for in the incoming HttpServletRequest. - - - - - - A reference to a user-service (or UserDetailsService bean) Id - - - - - - - Registers the AuthenticationManager instance and allows its list of - AuthenticationProviders to be defined. Also allows you to define an alias to allow you to - reference the AuthenticationManager in your own beans. - - - - - - - Indicates that the contained user-service should be used as an authentication source. - - - - - - - - element which defines a password encoding strategy. Used by an authentication provider to - convert submitted passwords to hashed versions, for example. - - - - - - - - - - - - - Sets up an ldap authentication provider - - - - - - - Specifies that an LDAP provider should use an LDAP compare operation of the user's - password to authenticate the user - - - - - - - element which defines a password encoding strategy. Used by an authentication provider to - convert submitted passwords to hashed versions, for example. - - - - - - - - - - - - - - - - - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. - - - - - - An alias you wish to use for the AuthenticationManager bean (not required it you are using - a specific id) - - - - - - If set to true, the AuthenticationManger will attempt to clear any credentials data in the - returned Authentication object, once the user has been authenticated. - - - - - - Use this ObservationRegistry to collect metrics on various parts of the filter chain - - - - - - - - - Defines a reference to a Spring bean Id. - - - - - - A reference to a user-service (or UserDetailsService bean) Id - - - - - - - Creates an in-memory UserDetailsService from a properties file or a list of "user" child - elements. Usernames are converted to lower-case internally to allow for case-insensitive - lookups, so this should not be used if case-sensitivity is required. - - - - - - - Represents a user in the application. - - - - - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. - - - - - - - - - - The location of a Properties file where each line is in the format of - username=password,grantedAuthority[,grantedAuthority][,enabled|disabled] - - - - - - - - - The username assigned to the user. - - - - - - The password assigned to the user. This may be hashed if the corresponding authentication - provider supports hashing (remember to set the "hash" attribute of the "user-service" - element). This attribute be omitted in the case where the data will not be used for - authentication, but only for accessing authorities. If omitted, the namespace will - generate a random value, preventing its accidental use for authentication. Cannot be - empty. - - - - - - One of more authorities granted to the user. Separate authorities with a comma (but no - space). For example, "ROLE_USER,ROLE_ADMINISTRATOR" - - - - - - Can be set to "true" to mark an account as locked and unusable. - - - - - - Can be set to "true" to mark an account as disabled and unusable. - - - - - - - Causes creation of a JDBC-based UserDetailsService. - - - - - - A bean identifier, used for referring to the bean elsewhere in the context. - - - - - - - - - - The bean ID of the DataSource which provides the required tables. - - - - - - Defines a reference to a cache for use with a UserDetailsService. - - - - - - An SQL statement to query a username, password, and enabled status given a username. - Default is "select username,password,enabled from users where username = ?" - - - - - - An SQL statement to query for a user's granted authorities given a username. The default - is "select username, authority from authorities where username = ?" - - - - - - An SQL statement to query user's group authorities given a username. The default is - "select g.id, g.group_name, ga.authority from groups g, group_members gm, - group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id" - - - - - - A non-empty string prefix that will be added to role strings loaded from persistent - storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is - non-empty. - - - - - - - Element for configuration of the CsrfFilter for protection against CSRF. It also updates - the default RequestCache to only replay "GET" requests. - - - - - - - - - - Specifies if csrf protection should be disabled. Default false (i.e. CSRF protection is - enabled). - - - - - - The RequestMatcher instance to be used to determine if CSRF should be applied. Default is - any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS" - - - - - - The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by - LazyCsrfTokenRepository. - - - - - - The CsrfTokenRequestHandler to use. The default is CsrfTokenRequestAttributeHandler. - - - - - - - Element for configuration of the HeaderWritersFilter. Enables easy setting for the - X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers. - - - - - - - - - - - - - - - - - - - - - - - - - - Specifies if the default headers should be disabled. Default false. - - - - - - Specifies if headers should be disabled. Default false. - - - - - - - Adds support for HTTP Strict Transport Security (HSTS) - - - - - - - - - - Specifies if HTTP Strict Transport Security (HSTS) should be disabled. Default false. - - - - - - Specifies if subdomains should be included. Default true. - - - - - - Specifies the maximum amount of time the host should be considered a Known HSTS Host. - Default one year. - - - - - - The RequestMatcher instance to be used to determine if the header should be set. Default - is if HttpServletRequest.isSecure() is true. - - - - - - Specifies if preload should be included. Default false. - - - - - - - Element for configuration of CorsFilter. If no CorsFilter or CorsConfigurationSource is - specified a HandlerMappingIntrospector is used as the CorsConfigurationSource - - - - - - - - - - Defines a reference to a Spring bean Id. - - - - - - Specifies a bean id that is a CorsConfigurationSource used to construct the CorsFilter to - use - - - - - - - Adds support for HTTP Public Key Pinning (HPKP). - - - - - - - - - - - - - - - - - - The list with pins - - - - - - - - - - - A pin is specified using the base64-encoded SPKI fingerprint as value and the - cryptographic hash algorithm as attribute - - - - - - The cryptographic hash algorithm - - - - - - - - - Specifies if HTTP Public Key Pinning (HPKP) should be disabled. Default false. - - - - - - Specifies if subdomains should be included. Default false. - - - - - - Sets the value for the max-age directive of the Public-Key-Pins header. Default 60 days. - - - - - - Specifies if the browser should only report pin validation failures. Default true. - - - - - - Specifies the URI to which the browser should report pin validation failures. - - - - - - - Adds support for Content Security Policy (CSP) - - - - - - - - - - The security policy directive(s) for the Content-Security-Policy header or if report-only - is set to true, then the Content-Security-Policy-Report-Only header is used. - - - - - - Set to true, to enable the Content-Security-Policy-Report-Only header for reporting policy - violations only. Defaults to false. - - - - - - - Adds support for Referrer Policy - - - - - - - - - - The policies for the Referrer-Policy header. - - - - - - - - - - - - - - - - - - - Adds support for Feature Policy - - - - - - - - - - The security policy directive(s) for the Feature-Policy header. - - - - - - - Adds support for Permissions Policy - - - - - - - - - - The policies for the Permissions-Policy header. - - - - - - - Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for - every request - - - - - - - - - - Specifies if Cache Control should be disabled. Default false. - - - - - - - Enable basic clickjacking support for newer browsers (IE8+), will set the X-Frame-Options - header. - - - - - - - - - - If disabled, the X-Frame-Options header will not be included. Default false. - - - - - - Specify the policy to use for the X-Frame-Options-Header. - - - - - - - - - - - - - Specify the strategy to use when ALLOW-FROM is chosen. - - - - - - - - - - - - - Defines a reference to a Spring bean Id. - - - - - - Specify a value to use for the chosen strategy. - - - - - - Specify the request parameter to use for the origin when using a 'whitelist' or 'regexp' - based strategy. Default is 'from'. Deprecated ALLOW-FROM is an obsolete directive that no - longer works in modern browsers. Instead use Content-Security-Policy with the <a - href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors">frame-ancestors</a> - directive. - - - - - - - Enable basic XSS browser protection, supported by newer browsers (IE8+), will set the - X-XSS-Protection header. - - - - - - - - - - disable the X-XSS-Protection header. Default is 'false' meaning it is enabled. - - - - - - Specify the value for the X-Xss-Protection header. Defaults to "0". - - - - - - - - - - - - - - Add a X-Content-Type-Options header to the resopnse. Value is always 'nosniff'. - - - - - - - - - - If disabled, the X-Content-Type-Options header will not be included. Default false. - - - - - - - Adds support for Cross-Origin-Opener-Policy header - - - - - - - - - - The policies for the Cross-Origin-Opener-Policy header. - - - - - - - - - - - - - - Adds support for Cross-Origin-Embedder-Policy header - - - - - - - - - - The policies for the Cross-Origin-Embedder-Policy header. - - - - - - - - - - - - - Adds support for Cross-Origin-Resource-Policy header - - - - - - - - - - The policies for the Cross-Origin-Resource-Policy header. - - - - - - - - - - - - - - Add additional headers to the response. - - - - - - - - - - The name of the header to add. - - - - - - The value for the header. - - - - - - Defines a reference to a Spring bean Id. - - - - - - - - Used to indicate that a filter bean declaration should be incorporated into the security - filter chain. - - - - - - - - - - - The filter immediately after which the custom-filter should be placed in the chain. This - feature will only be needed by advanced users who wish to mix their own filters into the - security filter chain and have some knowledge of the standard Spring Security filters. The - filter names map to specific Spring Security implementation filters. - - - - - - The filter immediately before which the custom-filter should be placed in the chain - - - - - - The explicit position at which the custom-filter should be placed in the chain. Use if you - are replacing a standard filter. - - - - - - - - The filter immediately after which the custom-filter should be placed in the chain. This - feature will only be needed by advanced users who wish to mix their own filters into the - security filter chain and have some knowledge of the standard Spring Security filters. The - filter names map to specific Spring Security implementation filters. - - - - - - - - The filter immediately before which the custom-filter should be placed in the chain - - - - - - - - The explicit position at which the custom-filter should be placed in the chain. Use if you - are replacing a standard filter. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java index 23275994640..66358af6fd2 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -248,6 +248,7 @@ void serializeCurrentVersionClasses(Class clazz) throws Exception { @ParameterizedTest @MethodSource("getFilesToDeserialize") + @Disabled("The feature is only supported for versions >= 6.3") void shouldBeAbleToDeserializeClassFromPreviousVersion(Path filePath) { try (FileInputStream fileInputStream = new FileInputStream(filePath.toFile()); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyConfigurationTests.java deleted file mode 100644 index 254c8b08713..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyConfigurationTests.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authentication.TestAuthentication; -import org.springframework.security.authorization.AuthorizationProxyFactory; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link PrePostMethodSecurityConfiguration}. - * - * @author Evgeniy Cheban - * @author Josh Cummings - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class AuthorizationProxyConfigurationTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - AuthorizationProxyFactory proxyFactory; - - @WithMockUser - @Test - public void proxyWhenNotPreAuthorizedThenDenies() { - this.spring.register(DefaultsConfig.class).autowire(); - Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster()); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::makeToast) - .withMessage("Access Denied"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::extractBread) - .withMessage("Access Denied"); - } - - @WithMockUser(roles = "ADMIN") - @Test - public void proxyWhenPreAuthorizedThenAllows() { - this.spring.register(DefaultsConfig.class).autowire(); - Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster()); - toaster.makeToast(); - assertThat(toaster.extractBread()).isEqualTo("yummy"); - } - - @Test - public void proxyReactiveWhenNotPreAuthorizedThenDenies() { - this.spring.register(ReactiveDefaultsConfig.class).autowire(); - Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster()); - Authentication user = TestAuthentication.authenticatedUser(); - StepVerifier - .create(toaster.reactiveMakeToast().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user))) - .verifyError(AccessDeniedException.class); - StepVerifier - .create(toaster.reactiveExtractBread().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user))) - .verifyError(AccessDeniedException.class); - } - - @Test - public void proxyReactiveWhenPreAuthorizedThenAllows() { - this.spring.register(ReactiveDefaultsConfig.class).autowire(); - Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster()); - Authentication admin = TestAuthentication.authenticatedAdmin(); - StepVerifier - .create(toaster.reactiveMakeToast().contextWrite(ReactiveSecurityContextHolder.withAuthentication(admin))) - .expectNext() - .verifyComplete(); - } - - @EnableMethodSecurity - @Configuration - static class DefaultsConfig { - - } - - @EnableReactiveMethodSecurity - @Configuration - static class ReactiveDefaultsConfig { - - } - - static class Toaster { - - @PreAuthorize("hasRole('ADMIN')") - void makeToast() { - - } - - @PostAuthorize("hasRole('ADMIN')") - String extractBread() { - return "yummy"; - } - - @PreAuthorize("hasRole('ADMIN')") - Mono reactiveMakeToast() { - return Mono.empty(); - } - - @PostAuthorize("hasRole('ADMIN')") - Mono reactiveExtractBread() { - return Mono.just("yummy"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java index 145f344d126..9a9ff57da49 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java @@ -18,8 +18,6 @@ import reactor.core.publisher.Mono; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -47,20 +45,4 @@ public boolean check(Authentication authentication, String message) { return message != null && message.contains(authentication.getName()); } - public AuthorizationResult checkResult(boolean result) { - return new AuthzResult(result); - } - - public Mono checkReactiveResult(boolean result) { - return Mono.just(checkResult(result)); - } - - public static class AuthzResult extends AuthorizationDecision { - - public AuthzResult(boolean granted) { - super(granted); - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java index 3fff7b52e48..1411eb92f65 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,42 +16,23 @@ package org.springframework.security.config.annotation.method.configuration; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.List; import jakarta.annotation.security.DenyAll; import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.RolesAllowed; -import org.aopalliance.intercept.MethodInvocation; -import org.springframework.context.ApplicationContext; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; import org.springframework.security.access.annotation.Secured; -import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; -import org.springframework.security.authorization.AuthorizationResult; -import org.springframework.security.authorization.method.AuthorizeReturnObject; -import org.springframework.security.authorization.method.HandleAuthorizationDenied; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.parameters.P; -import org.springframework.util.StringUtils; /** * @author Rob Winch */ -@MethodSecurityService.Mask("classmask") public interface MethodSecurityService { @PreAuthorize("denyAll") @@ -72,9 +53,6 @@ public interface MethodSecurityService { @RolesAllowed("ADMIN") String jsr250RolesAllowed(); - @RolesAllowed("USER") - String jsr250RolesAllowedUser(); - @Secured({ "ROLE_USER", "RUN_AS_SUPER" }) Authentication runAs(); @@ -90,9 +68,6 @@ public interface MethodSecurityService { @PreAuthorize("hasRole('ADMIN')") void preAuthorizeAdmin(); - @PreAuthorize("hasRole('USER')") - void preAuthorizeUser(); - @PreAuthorize("hasPermission(#object,'read')") String hasPermission(String object); @@ -115,248 +90,8 @@ public interface MethodSecurityService { @PostAuthorize("returnObject.size == 2") List manyAnnotations(List array); - @PreFilter("filterObject != 'DropOnPreFilter'") - @PreAuthorize("#list.remove('DropOnPreAuthorize')") - @Secured("ROLE_SECURED") - @RolesAllowed("JSR250") - @PostAuthorize("#list.remove('DropOnPostAuthorize')") - @PostFilter("filterObject != 'DropOnPostFilter'") - List allAnnotations(List list); - @RequireUserRole @RequireAdminRole void repeatedAnnotations(); - @PreAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class) - String preAuthorizeGetCardNumberIfAdmin(String cardNumber); - - @PreAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class) - String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber); - - @PreAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class) - String preAuthorizeThrowAccessDeniedManually(); - - @PostAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class) - String postAuthorizeGetCardNumberIfAdmin(String cardNumber); - - @PostAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class) - String postAuthorizeThrowAccessDeniedManually(); - - @PreAuthorize("denyAll()") - @Mask("methodmask") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) - String preAuthorizeDeniedMethodWithMaskAnnotation(); - - @PreAuthorize("denyAll()") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) - String preAuthorizeDeniedMethodWithNoMaskAnnotation(); - - @NullDenied(role = "ADMIN") - String postAuthorizeDeniedWithNullDenied(); - - @PostAuthorize("denyAll()") - @Mask("methodmask") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) - String postAuthorizeDeniedMethodWithMaskAnnotation(); - - @PostAuthorize("denyAll()") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) - String postAuthorizeDeniedMethodWithNoMaskAnnotation(); - - @PreAuthorize("hasRole('ADMIN')") - @Mask(expression = "@myMasker.getMask()") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) - String preAuthorizeWithMaskAnnotationUsingBean(); - - @PostAuthorize("hasRole('ADMIN')") - @Mask(expression = "@myMasker.getMask(returnObject)") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) - String postAuthorizeWithMaskAnnotationUsingBean(); - - @AuthorizeReturnObject - UserRecordWithEmailProtected getUserRecordWithEmailProtected(); - - @PreAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = UserFallbackDeniedHandler.class) - UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized(); - - @PreAuthorize("@authz.checkResult(#result)") - @PostAuthorize("@authz.checkResult(!#result)") - @HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class) - String checkCustomResult(boolean result); - - class StarMaskingHandler implements MethodAuthorizationDeniedHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) { - return "***"; - } - - } - - class StartMaskingHandlerChild extends StarMaskingHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) { - return super.handleDeniedInvocation(methodInvocation, result) + "-child"; - } - - } - - class MaskAnnotationHandler implements MethodAuthorizationDeniedHandler { - - MaskValueResolver maskValueResolver; - - MaskAnnotationHandler(ApplicationContext context) { - this.maskValueResolver = new MaskValueResolver(context); - } - - public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { - Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class); - if (mask == null) { - mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod().getDeclaringClass(), Mask.class); - } - return this.maskValueResolver.resolveValue(mask, methodInvocation, null); - } - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, - AuthorizationResult authorizationResult) { - return handle(methodInvocation, authorizationResult); - } - - } - - class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedHandler { - - MaskValueResolver maskValueResolver; - - MaskAnnotationPostProcessor(ApplicationContext context) { - this.maskValueResolver = new MaskValueResolver(context); - } - - @Override - public Object handleDeniedInvocation(MethodInvocation mi, AuthorizationResult authorizationResult) { - Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); - if (mask == null) { - mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class); - } - return this.maskValueResolver.resolveValue(mask, mi, null); - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, - AuthorizationResult authorizationResult) { - MethodInvocation mi = methodInvocationResult.getMethodInvocation(); - Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); - if (mask == null) { - mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class); - } - return this.maskValueResolver.resolveValue(mask, mi, methodInvocationResult.getResult()); - } - - } - - class MaskValueResolver { - - DefaultMethodSecurityExpressionHandler expressionHandler; - - MaskValueResolver(ApplicationContext context) { - this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); - this.expressionHandler.setApplicationContext(context); - } - - String resolveValue(Mask mask, MethodInvocation mi, Object returnObject) { - if (StringUtils.hasText(mask.value())) { - return mask.value(); - } - Expression expression = this.expressionHandler.getExpressionParser().parseExpression(mask.expression()); - EvaluationContext evaluationContext = this.expressionHandler - .createEvaluationContext(() -> SecurityContextHolder.getContext().getAuthentication(), mi); - if (returnObject != null) { - this.expressionHandler.setReturnObject(returnObject, evaluationContext); - } - return expression.getValue(evaluationContext, String.class); - } - - } - - class PostMaskingPostProcessor implements MethodAuthorizationDeniedHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, - AuthorizationResult authorizationResult) { - return "***"; - } - - } - - class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedHandler { - - static String MASK = "****-****-****-"; - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, - AuthorizationResult authorizationResult) { - return "***"; - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult contextObject, AuthorizationResult result) { - String cardNumber = (String) contextObject.getResult(); - return MASK + cardNumber.substring(cardNumber.length() - 4); - } - - } - - class NullPostProcessor implements MethodAuthorizationDeniedHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, - AuthorizationResult authorizationResult) { - return null; - } - - } - - @Target({ ElementType.METHOD, ElementType.TYPE }) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @interface Mask { - - String value() default ""; - - String expression() default ""; - - } - - @Target({ ElementType.METHOD, ElementType.TYPE }) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @PostAuthorize("hasRole('{role}')") - @HandleAuthorizationDenied(handlerClass = NullPostProcessor.class) - @interface NullDenied { - - String role(); - - } - - class UserFallbackDeniedHandler implements MethodAuthorizationDeniedHandler { - - private static final UserRecordWithEmailProtected FALLBACK = new UserRecordWithEmailProtected("Protected", - "Protected"); - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, - AuthorizationResult authorizationResult) { - return FALLBACK; - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java index a5c78f6d962..ee664f5a45e 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java @@ -28,14 +28,4 @@ MethodSecurityService service() { return new MethodSecurityServiceImpl(); } - @Bean - ReactiveMethodSecurityService reactiveService() { - return new ReactiveMethodSecurityServiceImpl(); - } - - @Bean - Authz authz() { - return new Authz(); - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java index 6bf15c304d7..18a2b7eb59d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -18,8 +18,6 @@ import java.util.List; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -58,11 +56,6 @@ public String jsr250RolesAllowed() { return null; } - @Override - public String jsr250RolesAllowedUser() { - return null; - } - @Override public Authentication runAs() { return SecurityContextHolder.getContext().getAuthentication(); @@ -80,10 +73,6 @@ public void preAuthorizeBean(boolean b) { public void preAuthorizeAdmin() { } - @Override - public void preAuthorizeUser() { - } - @Override public String preAuthorizePermitAll() { return null; @@ -119,88 +108,8 @@ public List manyAnnotations(List object) { return object; } - @Override - public List allAnnotations(List list) { - return null; - } - @Override public void repeatedAnnotations() { } - @Override - public String postAuthorizeGetCardNumberIfAdmin(String cardNumber) { - return cardNumber; - } - - @Override - public String preAuthorizeGetCardNumberIfAdmin(String cardNumber) { - return cardNumber; - } - - @Override - public String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber) { - return cardNumber; - } - - @Override - public String preAuthorizeThrowAccessDeniedManually() { - throw new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)); - } - - @Override - public String postAuthorizeThrowAccessDeniedManually() { - throw new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)); - } - - @Override - public String preAuthorizeDeniedMethodWithMaskAnnotation() { - return "ok"; - } - - @Override - public String preAuthorizeDeniedMethodWithNoMaskAnnotation() { - return "ok"; - } - - @Override - public String postAuthorizeDeniedWithNullDenied() { - return "ok"; - } - - @Override - public String postAuthorizeDeniedMethodWithMaskAnnotation() { - return "ok"; - } - - @Override - public String postAuthorizeDeniedMethodWithNoMaskAnnotation() { - return "ok"; - } - - @Override - public String preAuthorizeWithMaskAnnotationUsingBean() { - return "ok"; - } - - @Override - public String postAuthorizeWithMaskAnnotationUsingBean() { - return "ok"; - } - - @Override - public UserRecordWithEmailProtected getUserRecordWithEmailProtected() { - return new UserRecordWithEmailProtected("username", "useremail@example.com"); - } - - @Override - public UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized() { - return new UserRecordWithEmailProtected("username", "useremail@example.com"); - } - - @Override - public String checkCustomResult(boolean result) { - return "ok"; - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MyMasker.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MyMasker.java deleted file mode 100644 index a60abd87fdd..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MyMasker.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration; - -public class MyMasker { - - public String getMask(String value) { - return value + "-masked"; - } - - public String getMask() { - return "mask"; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java index 69a42a7e473..7e049bed7cd 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -17,14 +17,9 @@ package org.springframework.security.config.annotation.method.configuration; import java.io.Serializable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Supplier; @@ -41,7 +36,6 @@ import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Role; import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.security.access.AccessDeniedException; @@ -52,24 +46,12 @@ import org.springframework.security.access.annotation.Jsr250BusinessServiceImpl; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; -import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.access.prepost.PostFilter; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor; import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; -import org.springframework.security.authorization.method.AuthorizeReturnObject; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; import org.springframework.security.authorization.method.MethodInvocationResult; -import org.springframework.security.authorization.method.PrePostTemplateDefaults; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.test.SpringTestContext; @@ -88,12 +70,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; /** @@ -460,6 +439,7 @@ public void configureWhenAspectJThenRegistersAspects() { assertThat(this.spring.getContext().containsBean("annotationSecurityAspect$0")).isFalse(); } + // gh-13572 @Test public void configureWhenBeanOverridingDisallowedThenWorks() { this.spring.register(MethodSecurityServiceConfig.class, BusinessServiceConfig.class) @@ -467,506 +447,10 @@ public void configureWhenBeanOverridingDisallowedThenWorks() { .autowire(); } - @WithMockUser(roles = "ADMIN") - @Test - public void methodSecurityAdminWhenRoleHierarchyBeanAvailableThenUses() { - this.spring.register(RoleHierarchyConfig.class, MethodSecurityServiceConfig.class).autowire(); - this.methodSecurityService.preAuthorizeUser(); - this.methodSecurityService.securedUser(); - this.methodSecurityService.jsr250RolesAllowedUser(); - } - - @WithMockUser - @Test - public void methodSecurityUserWhenRoleHierarchyBeanAvailableThenUses() { - this.spring.register(RoleHierarchyConfig.class, MethodSecurityServiceConfig.class).autowire(); - this.methodSecurityService.preAuthorizeUser(); - this.methodSecurityService.securedUser(); - this.methodSecurityService.jsr250RolesAllowedUser(); - } - - @WithMockUser(roles = "ADMIN") - @Test - public void methodSecurityAdminWhenAuthorizationEventPublisherBeanAvailableThenUses() { - this.spring - .register(RoleHierarchyConfig.class, MethodSecurityServiceConfig.class, - AuthorizationEventPublisherConfig.class) - .autowire(); - this.methodSecurityService.preAuthorizeUser(); - this.methodSecurityService.securedUser(); - this.methodSecurityService.jsr250RolesAllowedUser(); - } - - @WithMockUser - @Test - public void methodSecurityUserWhenAuthorizationEventPublisherBeanAvailableThenUses() { - this.spring - .register(RoleHierarchyConfig.class, MethodSecurityServiceConfig.class, - AuthorizationEventPublisherConfig.class) - .autowire(); - this.methodSecurityService.preAuthorizeUser(); - this.methodSecurityService.securedUser(); - this.methodSecurityService.jsr250RolesAllowedUser(); - } - - @Test - public void allAnnotationsWhenAdviceBeforeOffsetPreFilterThenReturnsFilteredList() { - this.spring.register(ReturnBeforeOffsetPreFilterConfig.class).autowire(); - List list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize", - "DropOnPostFilter", "DoNotDrop"); - List filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list)); - assertThat(filtered).hasSize(5); - assertThat(filtered).containsExactly("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize", - "DropOnPostFilter", "DoNotDrop"); - } - - @Test - public void allAnnotationsWhenAdviceBeforeOffsetPreAuthorizeThenReturnsFilteredList() { - this.spring.register(ReturnBeforeOffsetPreAuthorizeConfig.class).autowire(); - List list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize", - "DropOnPostFilter", "DoNotDrop"); - List filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list)); - assertThat(filtered).hasSize(4); - assertThat(filtered).containsExactly("DropOnPreAuthorize", "DropOnPostAuthorize", "DropOnPostFilter", - "DoNotDrop"); - } - - @Test - public void allAnnotationsWhenAdviceBeforeOffsetSecuredThenReturnsFilteredList() { - this.spring.register(ReturnBeforeOffsetSecuredConfig.class).autowire(); - List list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize", - "DropOnPostFilter", "DoNotDrop"); - List filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list)); - assertThat(filtered).hasSize(3); - assertThat(filtered).containsExactly("DropOnPostAuthorize", "DropOnPostFilter", "DoNotDrop"); - } - - @Test - @WithMockUser - public void allAnnotationsWhenAdviceBeforeOffsetJsr250WithInsufficientRolesThenFails() { - this.spring.register(ReturnBeforeOffsetJsr250Config.class).autowire(); - List list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize", - "DropOnPostFilter", "DoNotDrop"); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.methodSecurityService.allAnnotations(new ArrayList<>(list))); - } - - @Test - @WithMockUser(roles = "SECURED") - public void allAnnotationsWhenAdviceBeforeOffsetJsr250ThenReturnsFilteredList() { - this.spring.register(ReturnBeforeOffsetJsr250Config.class).autowire(); - List list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize", - "DropOnPostFilter", "DoNotDrop"); - List filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list)); - assertThat(filtered).hasSize(3); - assertThat(filtered).containsExactly("DropOnPostAuthorize", "DropOnPostFilter", "DoNotDrop"); - } - - @Test - @WithMockUser(roles = { "SECURED" }) - public void allAnnotationsWhenAdviceBeforeOffsetPostAuthorizeWithInsufficientRolesThenFails() { - this.spring.register(ReturnBeforeOffsetPostAuthorizeConfig.class).autowire(); - List list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize", - "DropOnPostFilter", "DoNotDrop"); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.methodSecurityService.allAnnotations(new ArrayList<>(list))); - } - - @Test - @WithMockUser(roles = { "SECURED", "JSR250" }) - public void allAnnotationsWhenAdviceBeforeOffsetPostAuthorizeThenReturnsFilteredList() { - this.spring.register(ReturnBeforeOffsetPostAuthorizeConfig.class).autowire(); - List list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize", - "DropOnPostFilter", "DoNotDrop"); - List filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list)); - assertThat(filtered).hasSize(3); - assertThat(filtered).containsExactly("DropOnPostAuthorize", "DropOnPostFilter", "DoNotDrop"); - } - - @Test - @WithMockUser(roles = { "SECURED", "JSR250" }) - public void allAnnotationsWhenAdviceBeforeOffsetPostFilterThenReturnsFilteredList() { - this.spring.register(ReturnBeforeOffsetPostFilterConfig.class).autowire(); - List list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize", - "DropOnPostFilter", "DoNotDrop"); - List filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list)); - assertThat(filtered).hasSize(2); - assertThat(filtered).containsExactly("DropOnPostFilter", "DoNotDrop"); - } - - @Test - @WithMockUser(roles = { "SECURED", "JSR250" }) - public void allAnnotationsWhenAdviceAfterAllOffsetThenReturnsFilteredList() { - this.spring.register(ReturnAfterAllOffsetConfig.class).autowire(); - List list = Arrays.asList("DropOnPreFilter", "DropOnPreAuthorize", "DropOnPostAuthorize", - "DropOnPostFilter", "DoNotDrop"); - List filtered = this.methodSecurityService.allAnnotations(new ArrayList<>(list)); - assertThat(filtered).hasSize(1); - assertThat(filtered).containsExactly("DoNotDrop"); - } - - @Test - @WithMockUser - public void methodeWhenParameterizedPreAuthorizeMetaAnnotationThenPasses() { - this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); - MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); - assertThat(service.hasRole("USER")).isTrue(); - } - - @Test - @WithMockUser - public void methodRoleWhenPreAuthorizeMetaAnnotationHardcodedParameterThenPasses() { - this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); - MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); - assertThat(service.hasUserRole()).isTrue(); - } - - @Test - public void methodWhenParameterizedAnnotationThenFails() { - this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); - MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(service::placeholdersOnlyResolvedByMetaAnnotations); - } - - @Test - @WithMockUser(authorities = "SCOPE_message:read") - public void methodWhenMultiplePlaceholdersHasAuthorityThenPasses() { - this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); - MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); - assertThat(service.readMessage()).isEqualTo("message"); - } - - @Test - @WithMockUser(roles = "ADMIN") - public void methodWhenMultiplePlaceholdersHasRoleThenPasses() { - this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); - MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); - assertThat(service.readMessage()).isEqualTo("message"); - } - - @Test - @WithMockUser - public void methodWhenPostAuthorizeMetaAnnotationThenAuthorizes() { - this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); - MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); - service.startsWithDave("daveMatthews"); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> service.startsWithDave("jenniferHarper")); - } - - @Test - @WithMockUser - public void methodWhenPreFilterMetaAnnotationThenFilters() { - this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); - MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); - assertThat(service.parametersContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul")))) - .containsExactly("dave"); - } - - @Test - @WithMockUser - public void methodWhenPostFilterMetaAnnotationThenFilters() { - this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); - MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); - assertThat(service.resultsContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul")))) - .containsExactly("dave"); - } - - @Test - @WithMockUser(authorities = "airplane:read") - public void findByIdWhenAuthorizedResultThenAuthorizes() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - Flight flight = flights.findById("1"); - assertThatNoException().isThrownBy(flight::getAltitude); - assertThatNoException().isThrownBy(flight::getSeats); - } - - @Test - @WithMockUser(authorities = "seating:read") - public void findByIdWhenUnauthorizedResultThenDenies() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - Flight flight = flights.findById("1"); - assertThatNoException().isThrownBy(flight::getSeats); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude); - } - - @Test - @WithMockUser(authorities = "seating:read") - public void findAllWhenUnauthorizedResultThenDenies() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - flights.findAll().forEachRemaining((flight) -> { - assertThatNoException().isThrownBy(flight::getSeats); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude); - }); - } - - @Test - public void removeWhenAuthorizedResultThenRemoves() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - flights.remove("1"); - } - - @Test - @WithMockUser(authorities = "airplane:read") - public void findAllWhenPostFilterThenFilters() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - flights.findAll() - .forEachRemaining((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName) - .doesNotContain("Kevin Mitnick")); - } - - @Test - @WithMockUser(authorities = "airplane:read") - public void findAllWhenPreFilterThenFilters() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - flights.findAll().forEachRemaining((flight) -> { - flight.board(new ArrayList<>(List.of("John"))); - assertThat(flight.getPassengers()).extracting(Passenger::getName).doesNotContain("John"); - flight.board(new ArrayList<>(List.of("John Doe"))); - assertThat(flight.getPassengers()).extracting(Passenger::getName).contains("John Doe"); - }); - } - - @Test - @WithMockUser(authorities = "seating:read") - public void findAllWhenNestedPreAuthorizeThenAuthorizes() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - flights.findAll().forEachRemaining((flight) -> { - List passengers = flight.getPassengers(); - passengers.forEach((passenger) -> assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(passenger::getName)); - }); - } - - @Test - @WithMockUser - void getCardNumberWhenPostAuthorizeAndNotAdminThenReturnMasked() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - MethodSecurityService.CardNumberMaskingPostProcessor.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String cardNumber = service.postAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111"); - assertThat(cardNumber).isEqualTo("****-****-****-1111"); - } - - @Test - @WithMockUser - void getCardNumberWhenPreAuthorizeAndNotAdminThenReturnMasked() { - this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String cardNumber = service.preAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111"); - assertThat(cardNumber).isEqualTo("***"); - } - - @Test - @WithMockUser - void getCardNumberWhenPreAuthorizeAndNotAdminAndChildHandlerThenResolveCorrectHandlerAndReturnMasked() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class, - MethodSecurityService.StartMaskingHandlerChild.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String cardNumber = service.preAuthorizeWithHandlerChildGetCardNumberIfAdmin("4444-3333-2222-1111"); - assertThat(cardNumber).isEqualTo("***-child"); - } - - @Test - @WithMockUser(roles = "ADMIN") - void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenHandled() { - this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - assertThat(service.preAuthorizeThrowAccessDeniedManually()).isEqualTo("***"); - } - - @Test - @WithMockUser(roles = "ADMIN") - void postAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPostAuthorizeThenHandled() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.PostMaskingPostProcessor.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - assertThat(service.postAuthorizeThrowAccessDeniedManually()).isEqualTo("***"); - } - - @Test - @WithMockUser - void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String result = service.preAuthorizeDeniedMethodWithMaskAnnotation(); - assertThat(result).isEqualTo("methodmask"); - } - - @Test - @WithMockUser - void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String result = service.preAuthorizeDeniedMethodWithNoMaskAnnotation(); - assertThat(result).isEqualTo("classmask"); - } - - @Test - @WithMockUser - void postAuthorizeWhenNullDeniedMetaAnnotationThanWorks() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MetaAnnotationPlaceholderConfig.class, - MethodSecurityService.NullPostProcessor.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String result = service.postAuthorizeDeniedWithNullDenied(); - assertThat(result).isNull(); - } - - @Test - @WithMockUser - void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String result = service.postAuthorizeDeniedMethodWithMaskAnnotation(); - assertThat(result).isEqualTo("methodmask"); - } - - @Test - @WithMockUser - void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String result = service.postAuthorizeDeniedMethodWithNoMaskAnnotation(); - assertThat(result).isEqualTo("classmask"); - } - - @Test - @WithMockUser - void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class, - MyMasker.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String result = service.postAuthorizeWithMaskAnnotationUsingBean(); - assertThat(result).isEqualTo("ok-masked"); - } - - @Test - @WithMockUser(roles = "ADMIN") - void postAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class, - MyMasker.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String result = service.postAuthorizeWithMaskAnnotationUsingBean(); - assertThat(result).isEqualTo("ok"); - } - - @Test - @WithMockUser - void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class, - MyMasker.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String result = service.preAuthorizeWithMaskAnnotationUsingBean(); - assertThat(result).isEqualTo("mask"); - } - - @Test - @WithMockUser(roles = "ADMIN") - void preAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class, - MyMasker.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - String result = service.preAuthorizeWithMaskAnnotationUsingBean(); - assertThat(result).isEqualTo("ok"); - } - - @Test - @WithMockUser - void getUserWhenAuthorizedAndUserEmailIsProtectedAndNotAuthorizedThenReturnEmailMasked() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - UserRecordWithEmailProtected.EmailMaskingPostProcessor.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - UserRecordWithEmailProtected user = service.getUserRecordWithEmailProtected(); - assertThat(user.email()).isEqualTo("use******@example.com"); - assertThat(user.name()).isEqualTo("username"); - } - - @Test - @WithMockUser - void getUserWhenNotAuthorizedAndHandlerFallbackValueThenReturnFallbackValue() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.UserFallbackDeniedHandler.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - UserRecordWithEmailProtected user = service.getUserWithFallbackWhenUnauthorized(); - assertThat(user.email()).isEqualTo("Protected"); - assertThat(user.name()).isEqualTo("Protected"); - } - - @Test - @WithMockUser - void getUserWhenNotAuthorizedThenHandlerUsesCustomAuthorizationDecision() { - this.spring.register(MethodSecurityServiceConfig.class, CustomResultConfig.class).autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - MethodAuthorizationDeniedHandler handler = this.spring.getContext() - .getBean(MethodAuthorizationDeniedHandler.class); - assertThat(service.checkCustomResult(false)).isNull(); - verify(handler).handleDeniedInvocation(any(), any(Authz.AuthzResult.class)); - verify(handler, never()).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class)); - clearInvocations(handler); - assertThat(service.checkCustomResult(true)).isNull(); - verify(handler).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class)); - verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class)); - } - private static Consumer disallowBeanOverriding() { return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false); } - private static Advisor returnAdvisor(int order) { - JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); - pointcut.setPattern(".*MethodSecurityServiceImpl.*"); - MethodInterceptor interceptor = (mi) -> mi.getArguments()[0]; - DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor); - advisor.setOrder(order); - return advisor; - } - - @Configuration - static class AuthzConfig { - - @Bean - Authz authz() { - return new Authz(); - } - - } - @Configuration @EnableCustomMethodSecurity static class CustomMethodSecurityServiceConfig { @@ -1143,341 +627,4 @@ Authz authz() { } - @Configuration - @EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true) - static class RoleHierarchyConfig { - - @Bean - static RoleHierarchy roleHierarchy() { - RoleHierarchyImpl roleHierarchyImpl = new RoleHierarchyImpl(); - roleHierarchyImpl.setHierarchy("ROLE_ADMIN > ROLE_USER"); - return roleHierarchyImpl; - } - - } - - @Import(OffsetConfig.class) - static class ReturnBeforeOffsetPreFilterConfig { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - Advisor returnBeforePreFilter() { - return returnAdvisor(AuthorizationInterceptorsOrder.PRE_FILTER.getOrder() + OffsetConfig.OFFSET - 1); - } - - } - - @Configuration - @Import(OffsetConfig.class) - static class ReturnBeforeOffsetPreAuthorizeConfig { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - Advisor returnBeforePreAuthorize() { - return returnAdvisor(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder() + OffsetConfig.OFFSET - 1); - } - - } - - @Configuration - @Import(OffsetConfig.class) - static class ReturnBeforeOffsetSecuredConfig { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - Advisor returnBeforeSecured() { - return returnAdvisor(AuthorizationInterceptorsOrder.SECURED.getOrder() + OffsetConfig.OFFSET - 1); - } - - } - - @Configuration - @Import(OffsetConfig.class) - static class ReturnBeforeOffsetJsr250Config { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - Advisor returnBeforeJsr250() { - return returnAdvisor(AuthorizationInterceptorsOrder.JSR250.getOrder() + OffsetConfig.OFFSET - 1); - } - - } - - @Configuration - @Import(OffsetConfig.class) - static class ReturnBeforeOffsetPostAuthorizeConfig { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - Advisor returnBeforePreAuthorize() { - return returnAdvisor(AuthorizationInterceptorsOrder.POST_AUTHORIZE.getOrder() + OffsetConfig.OFFSET - 1); - } - - } - - @Configuration - @Import(OffsetConfig.class) - static class ReturnBeforeOffsetPostFilterConfig { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - Advisor returnBeforePostFilter() { - return returnAdvisor(AuthorizationInterceptorsOrder.POST_FILTER.getOrder() + OffsetConfig.OFFSET - 1); - } - - } - - @Configuration - @Import(OffsetConfig.class) - static class ReturnAfterAllOffsetConfig { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - Advisor returnAfterAll() { - return returnAdvisor(AuthorizationInterceptorsOrder.POST_FILTER.getOrder() + OffsetConfig.OFFSET + 1); - } - - } - - @Configuration - @EnableMethodSecurity(offset = OffsetConfig.OFFSET, jsr250Enabled = true, securedEnabled = true) - static class OffsetConfig { - - static final int OFFSET = 2; - - @Bean - MethodSecurityService methodSecurityService() { - return new MethodSecurityServiceImpl(); - } - - @Bean - Authz authz() { - return new Authz(); - } - - } - - @Configuration - @EnableMethodSecurity - static class MetaAnnotationPlaceholderConfig { - - @Bean - PrePostTemplateDefaults methodSecurityDefaults() { - return new PrePostTemplateDefaults(); - } - - @Bean - MetaAnnotationService metaAnnotationService() { - return new MetaAnnotationService(); - } - - } - - static class MetaAnnotationService { - - @RequireRole(role = "#role") - boolean hasRole(String role) { - return true; - } - - @RequireRole(role = "'USER'") - boolean hasUserRole() { - return true; - } - - @PreAuthorize("hasRole({role})") - void placeholdersOnlyResolvedByMetaAnnotations() { - } - - @HasClaim(claim = "message:read", roles = { "'ADMIN'" }) - String readMessage() { - return "message"; - } - - @ResultStartsWith("dave") - String startsWithDave(String value) { - return value; - } - - @ParameterContains("dave") - List parametersContainDave(List list) { - return list; - } - - @ResultContains("dave") - List resultsContainDave(List list) { - return list; - } - - } - - @Retention(RetentionPolicy.RUNTIME) - @PreAuthorize("hasRole({role})") - @interface RequireRole { - - String role(); - - } - - @Retention(RetentionPolicy.RUNTIME) - @PreAuthorize("hasAuthority('SCOPE_{claim}') || hasAnyRole({roles})") - @interface HasClaim { - - String claim(); - - String[] roles() default {}; - - } - - @Retention(RetentionPolicy.RUNTIME) - @PostAuthorize("returnObject.startsWith('{value}')") - @interface ResultStartsWith { - - String value(); - - } - - @Retention(RetentionPolicy.RUNTIME) - @PreFilter("filterObject.contains('{value}')") - @interface ParameterContains { - - String value(); - - } - - @Retention(RetentionPolicy.RUNTIME) - @PostFilter("filterObject.contains('{value}')") - @interface ResultContains { - - String value(); - - } - - @EnableMethodSecurity - @Configuration - static class AuthorizeResultConfig { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static Customizer skipValueTypes() { - return (f) -> f.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes()); - } - - @Bean - FlightRepository flights() { - FlightRepository flights = new FlightRepository(); - Flight one = new Flight("1", 35000d, 35); - one.board(new ArrayList<>(List.of("Marie Curie", "Kevin Mitnick", "Ada Lovelace"))); - flights.save(one); - Flight two = new Flight("2", 32000d, 72); - two.board(new ArrayList<>(List.of("Albert Einstein"))); - flights.save(two); - return flights; - } - - @Bean - RoleHierarchy roleHierarchy() { - return RoleHierarchyImpl.withRolePrefix("").role("airplane:read").implies("seating:read").build(); - } - - } - - @AuthorizeReturnObject - static class FlightRepository { - - private final Map flights = new ConcurrentHashMap<>(); - - Iterator findAll() { - return this.flights.values().iterator(); - } - - Flight findById(String id) { - return this.flights.get(id); - } - - Flight save(Flight flight) { - this.flights.put(flight.getId(), flight); - return flight; - } - - void remove(String id) { - this.flights.remove(id); - } - - } - - @AuthorizeReturnObject - static class Flight { - - private final String id; - - private final Double altitude; - - private final Integer seats; - - private final List passengers = new ArrayList<>(); - - Flight(String id, Double altitude, Integer seats) { - this.id = id; - this.altitude = altitude; - this.seats = seats; - } - - String getId() { - return this.id; - } - - @PreAuthorize("hasAuthority('airplane:read')") - Double getAltitude() { - return this.altitude; - } - - @PreAuthorize("hasAuthority('seating:read')") - Integer getSeats() { - return this.seats; - } - - @PostAuthorize("hasAuthority('seating:read')") - @PostFilter("filterObject.name != 'Kevin Mitnick'") - List getPassengers() { - return this.passengers; - } - - @PreAuthorize("hasAuthority('seating:read')") - @PreFilter("filterObject.contains(' ')") - void board(List passengers) { - for (String passenger : passengers) { - this.passengers.add(new Passenger(passenger)); - } - } - - } - - public static class Passenger { - - String name; - - public Passenger(String name) { - this.name = name; - } - - @PreAuthorize("hasAuthority('airplane:read')") - public String getName() { - return this.name; - } - - } - - @EnableMethodSecurity - static class CustomResultConfig { - - MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class); - - @Bean - MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() { - return this.handler; - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java deleted file mode 100644 index 5fe335870d7..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.test.StepVerifier; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class PrePostReactiveMethodSecurityConfigurationTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - @WithMockUser - void getCardNumberWhenPostAuthorizeAndNotAdminThenReturnMasked() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - ReactiveMethodSecurityService.CardNumberMaskingPostProcessor.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.postAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111")) - .expectNext("****-****-****-1111") - .verifyComplete(); - } - - @Test - @WithMockUser - void getCardNumberWhenPreAuthorizeAndNotAdminThenReturnMasked() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.preAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111")) - .expectNext("***") - .verifyComplete(); - } - - @Test - @WithMockUser - void getCardNumberWhenPreAuthorizeAndNotAdminAndChildHandlerThenResolveCorrectHandlerAndReturnMasked() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class, - ReactiveMethodSecurityService.StartMaskingHandlerChild.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.preAuthorizeWithHandlerChildGetCardNumberIfAdmin("4444-3333-2222-1111")) - .expectNext("***-child") - .verifyComplete(); - } - - @Test - @WithMockUser - void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - ReactiveMethodSecurityService.MaskAnnotationHandler.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.preAuthorizeDeniedMethodWithMaskAnnotation()) - .expectNext("methodmask") - .verifyComplete(); - } - - @Test - @WithMockUser - void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - ReactiveMethodSecurityService.MaskAnnotationHandler.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.preAuthorizeDeniedMethodWithNoMaskAnnotation()) - .expectNext("classmask") - .verifyComplete(); - } - - @Test - @WithMockUser(roles = "ADMIN") - void postAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPostAuthorizeThenNotHandled() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - ReactiveMethodSecurityService.PostMaskingPostProcessor.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.postAuthorizeThrowAccessDeniedManually()).expectNext("***").verifyComplete(); - } - - @Test - @WithMockUser(roles = "ADMIN") - void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenHandled() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.preAuthorizeThrowAccessDeniedManually()).expectNext("***").verifyComplete(); - } - - @Test - @WithMockUser - void postAuthorizeWhenNullDeniedMetaAnnotationThanWorks() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.NullPostProcessor.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.postAuthorizeDeniedWithNullDenied()).verifyComplete(); - } - - @Test - @WithMockUser - void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.postAuthorizeDeniedMethodWithMaskAnnotation()) - .expectNext("methodmask") - .verifyComplete(); - } - - @Test - @WithMockUser - void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.postAuthorizeDeniedMethodWithNoMaskAnnotation()) - .expectNext("classmask") - .verifyComplete(); - } - - @Test - @WithMockUser - void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class, MyMasker.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.postAuthorizeWithMaskAnnotationUsingBean()) - .expectNext("ok-masked") - .verifyComplete(); - } - - @Test - @WithMockUser(roles = "ADMIN") - void postAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class, MyMasker.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.postAuthorizeWithMaskAnnotationUsingBean()).expectNext("ok").verifyComplete(); - } - - @Test - @WithMockUser - void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - ReactiveMethodSecurityService.MaskAnnotationHandler.class, MyMasker.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.preAuthorizeWithMaskAnnotationUsingBean()).expectNext("mask").verifyComplete(); - } - - @Test - @WithMockUser(roles = "ADMIN") - void preAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, - ReactiveMethodSecurityService.MaskAnnotationHandler.class, MyMasker.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.preAuthorizeWithMaskAnnotationUsingBean()).expectNext("ok").verifyComplete(); - } - - @Configuration - @EnableReactiveMethodSecurity - static class MethodSecurityServiceEnabledConfig { - - @Bean - ReactiveMethodSecurityService methodSecurityService() { - return new ReactiveMethodSecurityServiceImpl(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java index cf845ebb612..79149fd41c3 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,53 +16,22 @@ package org.springframework.security.config.annotation.method.configuration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; -import java.util.function.Function; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Role; import org.springframework.expression.EvaluationContext; -import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.access.prepost.PostFilter; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.access.prepost.PreFilter; -import org.springframework.security.authentication.TestAuthentication; -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor; -import org.springframework.security.authorization.method.AuthorizeReturnObject; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.config.Customizer; +import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.test.context.support.WithMockUser; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; /** * @author Tadaya Tsuyukubo @@ -72,13 +41,14 @@ public class ReactiveMethodSecurityConfigurationTests { public final SpringTestContext spring = new SpringTestContext(this); - @Autowired(required = false) + @Autowired DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler; @Test public void rolePrefixWithGrantedAuthorityDefaults() throws NoSuchMethodException { this.spring.register(WithRolePrefixConfiguration.class).autowire(); - Authentication authentication = TestAuthentication.authenticatedUser(authorities("CUSTOM_ABC")); + TestingAuthenticationToken authentication = new TestingAuthenticationToken("principal", "credential", + "CUSTOM_ABC"); MockMethodInvocation methodInvocation = new MockMethodInvocation(new Foo(), Foo.class, "bar", String.class); EvaluationContext context = this.methodSecurityExpressionHandler.createEvaluationContext(authentication, methodInvocation); @@ -92,7 +62,8 @@ public void rolePrefixWithGrantedAuthorityDefaults() throws NoSuchMethodExceptio @Test public void rolePrefixWithDefaultConfig() throws NoSuchMethodException { this.spring.register(ReactiveMethodSecurityConfiguration.class).autowire(); - Authentication authentication = TestAuthentication.authenticatedUser(authorities("ROLE_ABC")); + TestingAuthenticationToken authentication = new TestingAuthenticationToken("principal", "credential", + "ROLE_ABC"); MockMethodInvocation methodInvocation = new MockMethodInvocation(new Foo(), Foo.class, "bar", String.class); EvaluationContext context = this.methodSecurityExpressionHandler.createEvaluationContext(authentication, methodInvocation); @@ -104,7 +75,8 @@ public void rolePrefixWithDefaultConfig() throws NoSuchMethodException { @Test public void rolePrefixWithGrantedAuthorityDefaultsAndSubclassWithProxyingEnabled() throws NoSuchMethodException { this.spring.register(SubclassConfig.class).autowire(); - Authentication authentication = TestAuthentication.authenticatedUser(authorities("ROLE_ABC")); + TestingAuthenticationToken authentication = new TestingAuthenticationToken("principal", "credential", + "ROLE_ABC"); MockMethodInvocation methodInvocation = new MockMethodInvocation(new Foo(), Foo.class, "bar", String.class); EvaluationContext context = this.methodSecurityExpressionHandler.createEvaluationContext(authentication, methodInvocation); @@ -113,132 +85,6 @@ public void rolePrefixWithGrantedAuthorityDefaultsAndSubclassWithProxyingEnabled assertThat(root.hasRole("ABC")).isTrue(); } - @Test - public void findByIdWhenAuthorizedResultThenAuthorizes() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - Authentication pilot = TestAuthentication.authenticatedUser(authorities("airplane:read")); - StepVerifier - .create(flights.findById("1") - .flatMap(Flight::getAltitude) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot))) - .expectNextCount(1) - .verifyComplete(); - StepVerifier - .create(flights.findById("1") - .flatMap(Flight::getSeats) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot))) - .expectNextCount(1) - .verifyComplete(); - } - - @Test - public void findByIdWhenUnauthorizedResultThenDenies() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - Authentication pilot = TestAuthentication.authenticatedUser(authorities("seating:read")); - StepVerifier - .create(flights.findById("1") - .flatMap(Flight::getSeats) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot))) - .expectNextCount(1) - .verifyComplete(); - StepVerifier - .create(flights.findById("1") - .flatMap(Flight::getAltitude) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot))) - .verifyError(AccessDeniedException.class); - } - - @Test - public void findAllWhenUnauthorizedResultThenDenies() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - Authentication pilot = TestAuthentication.authenticatedUser(authorities("seating:read")); - StepVerifier - .create(flights.findAll() - .flatMap(Flight::getSeats) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot))) - .expectNextCount(2) - .verifyComplete(); - StepVerifier - .create(flights.findAll() - .flatMap(Flight::getAltitude) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot))) - .verifyError(AccessDeniedException.class); - } - - @Test - public void removeWhenAuthorizedResultThenRemoves() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - Authentication pilot = TestAuthentication.authenticatedUser(authorities("seating:read")); - StepVerifier.create(flights.remove("1").contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot))) - .verifyComplete(); - } - - @Test - public void findAllWhenPostFilterThenFilters() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - Authentication pilot = TestAuthentication.authenticatedUser(authorities("airplane:read")); - StepVerifier - .create(flights.findAll() - .flatMap(Flight::getPassengers) - .flatMap(Passenger::getName) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot))) - .expectNext("Marie Curie", "Ada Lovelace", "Albert Einstein") - .verifyComplete(); - } - - @Test - public void findAllWhenPreFilterThenFilters() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - Authentication pilot = TestAuthentication.authenticatedUser(authorities("airplane:read")); - StepVerifier - .create(flights.findAll() - .flatMap((flight) -> flight.board(Flux.just("John Doe", "John")).then(Mono.just(flight))) - .flatMap(Flight::getPassengers) - .flatMap(Passenger::getName) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot))) - .expectNext("Marie Curie", "Ada Lovelace", "John Doe", "Albert Einstein", "John Doe") - .verifyComplete(); - } - - @Test - public void findAllWhenNestedPreAuthorizeThenAuthorizes() { - this.spring.register(AuthorizeResultConfig.class).autowire(); - FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); - Authentication pilot = TestAuthentication.authenticatedUser(authorities("seating:read")); - StepVerifier - .create(flights.findAll() - .flatMap(Flight::getPassengers) - .flatMap(Passenger::getName) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(pilot))) - .verifyError(AccessDeniedException.class); - } - - @Test - @WithMockUser - void getUserWhenNotAuthorizedThenHandlerUsesCustomAuthorizationDecision() { - this.spring.register(MethodSecurityServiceConfig.class, CustomResultConfig.class).autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - MethodAuthorizationDeniedHandler handler = this.spring.getContext() - .getBean(MethodAuthorizationDeniedHandler.class); - assertThat(service.checkCustomResult(false).block()).isNull(); - verify(handler).handleDeniedInvocation(any(), any(Authz.AuthzResult.class)); - verify(handler, never()).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class)); - clearInvocations(handler); - assertThat(service.checkCustomResult(true).block()).isNull(); - verify(handler).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class)); - verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class)); - } - - private static Consumer authorities(String... authorities) { - return (builder) -> builder.authorities(authorities); - } - @Configuration @EnableReactiveMethodSecurity // this imports ReactiveMethodSecurityConfiguration static class WithRolePrefixConfiguration { @@ -262,130 +108,4 @@ public void bar(String param) { } - @EnableReactiveMethodSecurity - @Configuration - static class AuthorizeResultConfig { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static Customizer skipValueTypes() { - return (factory) -> factory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes()); - } - - @Bean - FlightRepository flights() { - FlightRepository flights = new FlightRepository(); - Flight one = new Flight("1", 35000d, 35); - one.board(Flux.just("Marie Curie", "Kevin Mitnick", "Ada Lovelace")).block(); - flights.save(one).block(); - Flight two = new Flight("2", 32000d, 72); - two.board(Flux.just("Albert Einstein")).block(); - flights.save(two).block(); - return flights; - } - - @Bean - Function> isNotKevin() { - return (passenger) -> passenger.getName().map((name) -> !name.equals("Kevin Mitnick")); - } - - } - - @AuthorizeReturnObject - static class FlightRepository { - - private final Map flights = new ConcurrentHashMap<>(); - - Flux findAll() { - return Flux.fromIterable(this.flights.values()); - } - - Mono findById(String id) { - return Mono.just(this.flights.get(id)); - } - - Mono save(Flight flight) { - this.flights.put(flight.getId(), flight); - return Mono.just(flight); - } - - Mono remove(String id) { - this.flights.remove(id); - return Mono.empty(); - } - - } - - @AuthorizeReturnObject - static class Flight { - - private final String id; - - private final Double altitude; - - private final Integer seats; - - private final List passengers = new ArrayList<>(); - - Flight(String id, Double altitude, Integer seats) { - this.id = id; - this.altitude = altitude; - this.seats = seats; - } - - String getId() { - return this.id; - } - - @PreAuthorize("hasAuthority('airplane:read')") - Mono getAltitude() { - return Mono.just(this.altitude); - } - - @PreAuthorize("hasAnyAuthority('seating:read', 'airplane:read')") - Mono getSeats() { - return Mono.just(this.seats); - } - - @PostAuthorize("hasAnyAuthority('seating:read', 'airplane:read')") - @PostFilter("@isNotKevin.apply(filterObject)") - Flux getPassengers() { - return Flux.fromIterable(this.passengers); - } - - @PreAuthorize("hasAnyAuthority('seating:read', 'airplane:read')") - @PreFilter("filterObject.contains(' ')") - Mono board(Flux passengers) { - return passengers.doOnNext((passenger) -> this.passengers.add(new Passenger(passenger))).then(); - } - - } - - public static class Passenger { - - String name; - - public Passenger(String name) { - this.name = name; - } - - @PreAuthorize("hasAuthority('airplane:read')") - public Mono getName() { - return Mono.just(this.name); - } - - } - - @EnableReactiveMethodSecurity - static class CustomResultConfig { - - MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class); - - @Bean - MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() { - return this.handler; - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java deleted file mode 100644 index 000dcb386a0..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.aopalliance.intercept.MethodInvocation; -import reactor.core.publisher.Mono; - -import org.springframework.context.ApplicationContext; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; -import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authorization.AuthorizationResult; -import org.springframework.security.authorization.method.HandleAuthorizationDenied; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodInvocationResult; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.util.StringUtils; - -/** - * @author Rob Winch - */ -@ReactiveMethodSecurityService.Mask("classmask") -public interface ReactiveMethodSecurityService { - - @PreAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class) - Mono preAuthorizeGetCardNumberIfAdmin(String cardNumber); - - @PreAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class) - Mono preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber); - - @PreAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class) - Mono preAuthorizeThrowAccessDeniedManually(); - - @PostAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class) - Mono postAuthorizeGetCardNumberIfAdmin(String cardNumber); - - @PostAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class) - Mono postAuthorizeThrowAccessDeniedManually(); - - @PreAuthorize("denyAll()") - @Mask("methodmask") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) - Mono preAuthorizeDeniedMethodWithMaskAnnotation(); - - @PreAuthorize("denyAll()") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) - Mono preAuthorizeDeniedMethodWithNoMaskAnnotation(); - - @NullDenied(role = "ADMIN") - Mono postAuthorizeDeniedWithNullDenied(); - - @PostAuthorize("denyAll()") - @Mask("methodmask") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) - Mono postAuthorizeDeniedMethodWithMaskAnnotation(); - - @PostAuthorize("denyAll()") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) - Mono postAuthorizeDeniedMethodWithNoMaskAnnotation(); - - @PreAuthorize("hasRole('ADMIN')") - @Mask(expression = "@myMasker.getMask()") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) - Mono preAuthorizeWithMaskAnnotationUsingBean(); - - @PostAuthorize("hasRole('ADMIN')") - @Mask(expression = "@myMasker.getMask(returnObject)") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) - Mono postAuthorizeWithMaskAnnotationUsingBean(); - - @PreAuthorize("@authz.checkReactiveResult(#result)") - @PostAuthorize("@authz.checkReactiveResult(!#result)") - @HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class) - Mono checkCustomResult(boolean result); - - class StarMaskingHandler implements MethodAuthorizationDeniedHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) { - return "***"; - } - - } - - class StartMaskingHandlerChild extends StarMaskingHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) { - return super.handleDeniedInvocation(methodInvocation, result) + "-child"; - } - - } - - class MaskAnnotationHandler implements MethodAuthorizationDeniedHandler { - - MaskValueResolver maskValueResolver; - - MaskAnnotationHandler(ApplicationContext context) { - this.maskValueResolver = new MaskValueResolver(context); - } - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) { - Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class); - if (mask == null) { - mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod().getDeclaringClass(), Mask.class); - } - return this.maskValueResolver.resolveValue(mask, methodInvocation, null); - } - - } - - class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedHandler { - - MaskValueResolver maskValueResolver; - - MaskAnnotationPostProcessor(ApplicationContext context) { - this.maskValueResolver = new MaskValueResolver(context); - } - - @Override - public Object handleDeniedInvocation(MethodInvocation mi, AuthorizationResult authorizationResult) { - Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); - if (mask == null) { - mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class); - } - return this.maskValueResolver.resolveValue(mask, mi, null); - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, - AuthorizationResult authorizationResult) { - MethodInvocation mi = methodInvocationResult.getMethodInvocation(); - Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); - if (mask == null) { - mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class); - } - return this.maskValueResolver.resolveValue(mask, mi, methodInvocationResult.getResult()); - } - - } - - class MaskValueResolver { - - DefaultMethodSecurityExpressionHandler expressionHandler; - - MaskValueResolver(ApplicationContext context) { - this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); - this.expressionHandler.setApplicationContext(context); - } - - Mono resolveValue(Mask mask, MethodInvocation mi, Object returnObject) { - if (StringUtils.hasText(mask.value())) { - return Mono.just(mask.value()); - } - Expression expression = this.expressionHandler.getExpressionParser().parseExpression(mask.expression()); - EvaluationContext evaluationContext = this.expressionHandler - .createEvaluationContext(() -> SecurityContextHolder.getContext().getAuthentication(), mi); - if (returnObject != null) { - this.expressionHandler.setReturnObject(returnObject, evaluationContext); - } - return Mono.just(expression.getValue(evaluationContext, String.class)); - } - - } - - class PostMaskingPostProcessor implements MethodAuthorizationDeniedHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, - AuthorizationResult authorizationResult) { - return "***"; - } - - } - - class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedHandler { - - static String MASK = "****-****-****-"; - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, - AuthorizationResult authorizationResult) { - return "***"; - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult contextObject, AuthorizationResult result) { - String cardNumber = (String) contextObject.getResult(); - return MASK + cardNumber.substring(cardNumber.length() - 4); - } - - } - - class NullPostProcessor implements MethodAuthorizationDeniedHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, - AuthorizationResult authorizationResult) { - return null; - } - - } - - @Target({ ElementType.METHOD, ElementType.TYPE }) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @interface Mask { - - String value() default ""; - - String expression() default ""; - - } - - @Target({ ElementType.METHOD, ElementType.TYPE }) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @PostAuthorize("hasRole('{value}')") - @HandleAuthorizationDenied(handlerClass = NullPostProcessor.class) - @interface NullDenied { - - String role(); - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java deleted file mode 100644 index acf50eb1130..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration; - -import reactor.core.publisher.Mono; - -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationDeniedException; - -public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurityService { - - @Override - public Mono preAuthorizeGetCardNumberIfAdmin(String cardNumber) { - return Mono.just(cardNumber); - } - - @Override - public Mono preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber) { - return Mono.just(cardNumber); - } - - @Override - public Mono preAuthorizeThrowAccessDeniedManually() { - return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false))); - } - - @Override - public Mono postAuthorizeGetCardNumberIfAdmin(String cardNumber) { - return Mono.just(cardNumber); - } - - @Override - public Mono postAuthorizeThrowAccessDeniedManually() { - return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false))); - } - - @Override - public Mono preAuthorizeDeniedMethodWithMaskAnnotation() { - return Mono.just("ok"); - } - - @Override - public Mono preAuthorizeDeniedMethodWithNoMaskAnnotation() { - return Mono.just("ok"); - } - - @Override - public Mono postAuthorizeDeniedWithNullDenied() { - return Mono.just("ok"); - } - - @Override - public Mono postAuthorizeDeniedMethodWithMaskAnnotation() { - return Mono.just("ok"); - } - - @Override - public Mono postAuthorizeDeniedMethodWithNoMaskAnnotation() { - return Mono.just("ok"); - } - - @Override - public Mono preAuthorizeWithMaskAnnotationUsingBean() { - return Mono.just("ok"); - } - - @Override - public Mono postAuthorizeWithMaskAnnotationUsingBean() { - return Mono.just("ok"); - } - - @Override - public Mono checkCustomResult(boolean result) { - return Mono.just("ok"); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/UserRecordWithEmailProtected.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/UserRecordWithEmailProtected.java deleted file mode 100644 index de3e9f72f2e..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/UserRecordWithEmailProtected.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration; - -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.authorization.AuthorizationResult; -import org.springframework.security.authorization.method.HandleAuthorizationDenied; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodInvocationResult; - -public class UserRecordWithEmailProtected { - - private final String name; - - private final String email; - - public UserRecordWithEmailProtected(String name, String email) { - this.name = name; - this.email = email; - } - - public String name() { - return this.name; - } - - @PostAuthorize("hasRole('ADMIN')") - @HandleAuthorizationDenied(handlerClass = EmailMaskingPostProcessor.class) - public String email() { - return this.email; - } - - public static class EmailMaskingPostProcessor implements MethodAuthorizationDeniedHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, - AuthorizationResult authorizationResult) { - return "***"; - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, - AuthorizationResult authorizationResult) { - String email = (String) methodInvocationResult.getResult(); - return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/ApplicationConfig.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/ApplicationConfig.java deleted file mode 100644 index e422f719405..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/ApplicationConfig.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration.issue14637; - -import javax.sql.DataSource; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.Database; -import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; -import org.springframework.security.config.annotation.method.configuration.issue14637.domain.Entry; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -/** - * @author Josh Cummings - */ -@Configuration -@EnableJpaRepositories("org.springframework.security.config.annotation.method.configuration.issue14637.repo") -@EnableTransactionManagement -public class ApplicationConfig { - - @Bean - public DataSource dataSource() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); - return builder.setType(EmbeddedDatabaseType.HSQL).build(); - } - - @Bean - public LocalContainerEntityManagerFactoryBean entityManagerFactory() { - HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); - vendorAdapter.setDatabase(Database.HSQL); - vendorAdapter.setGenerateDdl(true); - vendorAdapter.setShowSql(true); - LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); - factory.setJpaVendorAdapter(vendorAdapter); - factory.setPackagesToScan(Entry.class.getPackage().getName()); - factory.setDataSource(dataSource()); - return factory; - } - - @Bean - public PlatformTransactionManager transactionManager() { - JpaTransactionManager txManager = new JpaTransactionManager(); - txManager.setEntityManagerFactory(entityManagerFactory().getObject()); - return txManager; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/Issue14637Tests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/Issue14637Tests.java deleted file mode 100644 index 1993e8d484c..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/Issue14637Tests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration.issue14637; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.config.annotation.method.configuration.issue14637.domain.Entry; -import org.springframework.security.config.annotation.method.configuration.issue14637.repo.EntryRepository; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Josh Cummings - */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = { ApplicationConfig.class, SecurityConfig.class }) -public class Issue14637Tests { - - @Autowired - private EntryRepository entries; - - @Test - @WithMockUser - public void authenticateWhenInvalidPasswordThenBadCredentialsException() { - Entry entry = new Entry(); - entry.setId(123L); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.entries.save(entry)); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/SecurityConfig.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/SecurityConfig.java deleted file mode 100644 index 5795e9c44dd..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/SecurityConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration.issue14637; - -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; - -/** - * @author Josh Cummings - */ -@Configuration -@EnableMethodSecurity -public class SecurityConfig { - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/domain/Entry.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/domain/Entry.java deleted file mode 100644 index 5bd054ce012..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/domain/Entry.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration.issue14637.domain; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; - -/** - * @author Josh Cummings - */ -@Entity -public class Entry { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private Long id; - - public Long getId() { - return this.id; - } - - public void setId(Long id) { - this.id = id; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/repo/EntryRepository.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/repo/EntryRepository.java deleted file mode 100644 index e23bb2669b0..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/issue14637/repo/EntryRepository.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration.issue14637.repo; - -import org.springframework.data.repository.CrudRepository; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.config.annotation.method.configuration.issue14637.domain.Entry; - -/** - * @author Josh Cummings - */ -public interface EntryRepository extends CrudRepository { - - @PreAuthorize("#entry.id == null") - Entry save(Entry entry); - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java index a41df95019e..2d95161fc6d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java @@ -47,9 +47,6 @@ import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; -import org.springframework.security.authentication.password.CompromisedPasswordCheckResult; -import org.springframework.security.authentication.password.CompromisedPasswordChecker; -import org.springframework.security.authentication.password.CompromisedPasswordException; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -63,8 +60,8 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.security.test.web.servlet.RequestCacheResultMatcher; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -398,41 +395,6 @@ public void configureWhenCustomDslAddedFromFactoriesAndDisablingUsingWithThenNot this.mockMvc.perform(formLogin()).andExpectAll(status().isNotFound(), unauthenticated()); } - @Test - void loginWhenCompromisePasswordCheckerConfiguredAndPasswordCompromisedThenUnauthorized() throws Exception { - this.spring - .register(SecurityEnabledConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class) - .autowire(); - this.mockMvc.perform(formLogin().password("password")) - .andExpectAll(status().isFound(), redirectedUrl("/login?error"), unauthenticated()); - } - - @Test - void loginWhenCompromisedPasswordAndRedirectIfPasswordExceptionThenRedirectedToResetPassword() throws Exception { - this.spring - .register(SecurityEnabledRedirectIfPasswordExceptionConfig.class, UserDetailsConfig.class, - CompromisedPasswordCheckerConfig.class) - .autowire(); - this.mockMvc.perform(formLogin().password("password")) - .andExpectAll(status().isFound(), redirectedUrl("/reset-password"), unauthenticated()); - } - - @Test - void loginWhenCompromisePasswordCheckerConfiguredAndPasswordNotCompromisedThenSuccess() throws Exception { - this.spring - .register(SecurityEnabledConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class) - .autowire(); - UserDetailsManager userDetailsManager = this.spring.getContext().getBean(UserDetailsManager.class); - UserDetails notCompromisedPwUser = User.withDefaultPasswordEncoder() - .username("user2") - .password("password2") - .roles("USER") - .build(); - userDetailsManager.createUser(notCompromisedPwUser); - this.mockMvc.perform(formLogin().user("user2").password("password2")) - .andExpectAll(status().isFound(), redirectedUrl("/"), authenticated()); - } - @RestController static class NameController { @@ -493,7 +455,7 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { static class UserDetailsConfig { @Bean - InMemoryUserDetailsManager userDetailsService() { + UserDetailsService userDetailsService() { // @formatter:off UserDetails user = User.withDefaultPasswordEncoder() .username("user") @@ -770,52 +732,4 @@ public void init(HttpSecurity builder) throws Exception { } - @Configuration(proxyBeanMethods = false) - static class CompromisedPasswordCheckerConfig { - - @Bean - TestCompromisedPasswordChecker compromisedPasswordChecker() { - return new TestCompromisedPasswordChecker(); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebSecurity - static class SecurityEnabledRedirectIfPasswordExceptionConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - return http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .formLogin((form) -> form - .failureHandler((request, response, exception) -> { - if (exception instanceof CompromisedPasswordException) { - response.sendRedirect("/reset-password"); - return; - } - response.sendRedirect("/login?error"); - }) - ) - .build(); - // @formatter:on - } - - } - - private static class TestCompromisedPasswordChecker implements CompromisedPasswordChecker { - - @Override - public CompromisedPasswordCheckResult check(String password) { - if ("password".equals(password)) { - return new CompromisedPasswordCheckResult(true); - } - return new CompromisedPasswordCheckResult(false); - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizedClientManagerConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizedClientManagerConfigurationTests.java index 7e58ce5b8e4..45d69c23233 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizedClientManagerConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizedClientManagerConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -38,6 +38,8 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; 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.oauth2.client.CommonOAuth2Provider; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider; @@ -50,7 +52,6 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest; import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; @@ -58,14 +59,17 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.OAuth2Error; @@ -73,10 +77,13 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.jwt.JoseHeaderNames; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -320,47 +327,6 @@ private void testJwtBearerGrant() { assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user"); } - @Test - public void authorizeWhenTokenExchangeAccessTokenResponseClientBeanThenUsed() { - this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); - testTokenExchangeGrant(); - } - - @Test - public void authorizeWhenTokenExchangeAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - testTokenExchangeGrant(); - } - - private void testTokenExchangeGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(accessTokenResponse); - - JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("auth0"); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(HttpServletRequest.class.getName(), this.request) - .attribute(HttpServletResponse.class.getName(), this.response) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(grantRequest.getSubjectToken()).isEqualTo(authentication.getToken()); - } - private static OAuth2AccessToken getExpiredAccessToken() { Instant expiresAt = Instant.now().minusSeconds(60); Instant issuedAt = expiresAt.minus(Duration.ofDays(1)); @@ -387,32 +353,37 @@ static class CustomAccessTokenResponseClientsConfig extends OAuth2ClientBaseConf @Bean OAuth2AccessTokenResponseClient authorizationCodeTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + return new MockAuthorizationCodeClient(); } @Bean OAuth2AccessTokenResponseClient refreshTokenTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + return new MockRefreshTokenClient(); } @Bean OAuth2AccessTokenResponseClient clientCredentialsTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + return new MockClientCredentialsClient(); } @Bean OAuth2AccessTokenResponseClient passwordTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + return new MockPasswordClient(); } @Bean OAuth2AccessTokenResponseClient jwtBearerTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + return new MockJwtBearerClient(); + } + + @Bean + OAuth2UserService oauth2UserService() { + return mock(DefaultOAuth2UserService.class); } @Bean - OAuth2AccessTokenResponseClient tokenExchangeTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + OAuth2UserService oidcUserService() { + return mock(OidcUserService.class); } } @@ -429,35 +400,28 @@ AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeProvider() { @Bean RefreshTokenOAuth2AuthorizedClientProvider refreshTokenProvider() { RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); + authorizedClientProvider.setAccessTokenResponseClient(new MockRefreshTokenClient()); return authorizedClientProvider; } @Bean ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsProvider() { ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); + authorizedClientProvider.setAccessTokenResponseClient(new MockClientCredentialsClient()); return authorizedClientProvider; } @Bean PasswordOAuth2AuthorizedClientProvider passwordProvider() { PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); + authorizedClientProvider.setAccessTokenResponseClient(new MockPasswordClient()); return authorizedClientProvider; } @Bean JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider() { JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); - return authorizedClientProvider; - } - - @Bean - TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider() { - TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); + authorizedClientProvider.setAccessTokenResponseClient(new MockJwtBearerClient()); return authorizedClientProvider; } @@ -465,10 +429,21 @@ TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvide abstract static class OAuth2ClientBaseConfig { + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) + .oauth2Login(Customizer.withDefaults()) + .oauth2Client(Customizer.withDefaults()); + return http.build(); + // @formatter:on + } + @Bean ClientRegistrationRepository clientRegistrationRepository() { // @formatter:off - return new InMemoryClientRegistrationRepository( + return new InMemoryClientRegistrationRepository(Arrays.asList( CommonOAuth2Provider.GOOGLE.getBuilder("google") .clientId("google-client-id") .clientSecret("google-client-secret") @@ -488,15 +463,7 @@ ClientRegistrationRepository clientRegistrationRepository() { .clientId("okta-client-id") .clientSecret("okta-client-secret") .authorizationGrantType(AuthorizationGrantType.JWT_BEARER) - .build(), - ClientRegistration.withRegistrationId("auth0") - .clientName("Auth0") - .clientId("auth0-client-id") - .clientSecret("auth0-client-secret") - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("user.read", "user.write") - .build()); + .build())); // @formatter:on } @@ -527,11 +494,51 @@ Consumer authorizedClientManagerConsumer() } - private static class MockAccessTokenResponseClient - implements OAuth2AccessTokenResponseClient { + private static class MockAuthorizationCodeClient + implements OAuth2AccessTokenResponseClient { + + @Override + public OAuth2AccessTokenResponse getTokenResponse( + OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) { + return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); + } + + } + + private static class MockRefreshTokenClient + implements OAuth2AccessTokenResponseClient { + + @Override + public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest authorizationGrantRequest) { + return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); + } + + } + + private static class MockClientCredentialsClient + implements OAuth2AccessTokenResponseClient { + + @Override + public OAuth2AccessTokenResponse getTokenResponse( + OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) { + return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); + } + + } + + private static class MockPasswordClient implements OAuth2AccessTokenResponseClient { + + @Override + public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest authorizationGrantRequest) { + return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); + } + + } + + private static class MockJwtBearerClient implements OAuth2AccessTokenResponseClient { @Override - public OAuth2AccessTokenResponse getTokenResponse(T authorizationGrantRequest) { + public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest authorizationGrantRequest) { return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java index 0aad4d777ca..c2d99042b02 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java @@ -40,10 +40,8 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; @@ -545,17 +543,6 @@ public void requestWhenMvcMatcherPathVariablesThenMatchesOnPathVariables() throw this.mvc.perform(request).andExpect(status().isOk()); request = get("/user/deny"); this.mvc.perform(request).andExpect(status().isUnauthorized()); - - UserDetails user = TestAuthentication.withUsername("taehong").build(); - Authentication authentication = TestAuthentication.authenticated(user); - request = get("/v2/user/{username}", user.getUsername()).with(authentication(authentication)); - this.mvc.perform(request).andExpect(status().isOk()); - - request = get("/v2/user/{username}", "withNoAuthentication"); - this.mvc.perform(request).andExpect(status().isUnauthorized()); - - request = get("/v2/user/{username}", "another").with(authentication(authentication)); - this.mvc.perform(request).andExpect(status().isForbidden()); } private static RequestPostProcessor remoteAddress(String remoteAddress) { @@ -609,20 +596,6 @@ public void getWhenAnonymousConfiguredAndLoggedInUserThenRespondsWithForbidden() this.mvc.perform(requestWithUser).andExpect(status().isForbidden()); } - @Test - public void getWhenNotConfigAndAuthenticatedThenRespondsWithForbidden() throws Exception { - this.spring.register(NotConfig.class, BasicController.class).autowire(); - MockHttpServletRequestBuilder requestWithUser = get("/").with(user("user")); - this.mvc.perform(requestWithUser).andExpect(status().isForbidden()); - } - - @Test - public void getWhenNotConfigAndNotAuthenticatedThenRespondsWithOk() throws Exception { - this.spring.register(NotConfig.class, BasicController.class).autowire(); - MockHttpServletRequestBuilder requestWithUser = get("/"); - this.mvc.perform(requestWithUser).andExpect(status().isOk()); - } - @Configuration @EnableWebSecurity static class GrantedAuthorityDefaultHasRoleConfig { @@ -1080,7 +1053,6 @@ SecurityFilterChain chain(HttpSecurity http) throws Exception { .httpBasic(withDefaults()) .authorizeHttpRequests((requests) -> requests .requestMatchers("/user/{username}").access(new WebExpressionAuthorizationManager("#username == 'user'")) - .requestMatchers("/v2/user/{username}").hasVariable("username").equalTo(Authentication::getName) ); // @formatter:on return http.build(); @@ -1094,11 +1066,6 @@ String path(@PathVariable("username") String username) { return username; } - @RequestMapping("/v2/user/{username}") - String pathV2(@PathVariable("username") String username) { - return username; - } - } } @@ -1169,24 +1136,6 @@ SecurityFilterChain chain(HttpSecurity http) throws Exception { } - @Configuration - @EnableWebSecurity - static class NotConfig { - - @Bean - SecurityFilterChain chain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .anyRequest().not().authenticated() - ); - // @formatter:on - return http.build(); - } - - } - @Configuration static class AuthorizationEventPublisherConfig { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java deleted file mode 100644 index dd7698e98db..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java +++ /dev/null @@ -1,589 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.web.reactive; - -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.function.Consumer; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.MediaType; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.oauth2.client.AuthorizationCodeReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; -import org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.JwtBearerReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.OAuth2AuthorizationContext; -import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.PasswordReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.TokenExchangeReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest; -import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; -import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.client.web.server.WebSessionServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; -import org.springframework.security.oauth2.jwt.JoseHeaderNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -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.spy; -import static org.mockito.Mockito.verify; - -/** - * Tests for - * {@link ReactiveOAuth2ClientConfiguration.ReactiveOAuth2AuthorizedClientManagerConfiguration}. - * - * @author Steve Riesenberg - */ -public class ReactiveOAuth2AuthorizedClientManagerConfigurationTests { - - private static ReactiveOAuth2AccessTokenResponseClient MOCK_RESPONSE_CLIENT; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private ReactiveOAuth2AuthorizedClientManager authorizedClientManager; - - @Autowired - private ReactiveClientRegistrationRepository clientRegistrationRepository; - - @Autowired(required = false) - private ServerOAuth2AuthorizedClientRepository authorizedClientRepository; - - @Autowired(required = false) - private ReactiveOAuth2AuthorizedClientService authorizedClientService; - - @Autowired(required = false) - private AuthorizationCodeReactiveOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider; - - private MockServerWebExchange exchange; - - @BeforeEach - @SuppressWarnings("unchecked") - public void setUp() { - MOCK_RESPONSE_CLIENT = mock(ReactiveOAuth2AccessTokenResponseClient.class); - MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); - this.exchange = MockServerWebExchange.builder(request).build(); - } - - @Test - public void loadContextWhenOAuth2ClientEnabledThenConfigured() { - this.spring.register(MinimalOAuth2ClientConfig.class).autowire(); - assertThat(this.authorizedClientManager).isNotNull(); - } - - @Test - public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId("google") - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - assertThatExceptionOfType(ClientAuthorizationRequiredException.class) - .isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest).block()) - .extracting(OAuth2AuthorizationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo("client_authorization_required"); - // @formatter:on - - verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class)); - } - - @Test - public void authorizeWhenAuthorizedClientServiceBeanThenUsed() { - this.spring.register(CustomAuthorizedClientServiceConfig.class).autowire(); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId("google") - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - assertThatExceptionOfType(ClientAuthorizationRequiredException.class) - .isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest).block()) - .extracting(OAuth2AuthorizationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo("client_authorization_required"); - // @formatter:on - - verify(this.authorizedClientService).loadAuthorizedClient(authorizeRequest.getClientRegistrationId(), - authentication.getName()); - } - - @Test - public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() { - this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); - testRefreshTokenGrant(); - } - - @Test - public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - testRefreshTokenGrant(); - } - - private void testRefreshTokenGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google") - .block(); - assertThat(clientRegistration).isNotNull(); - OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration, - authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken()); - this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.exchange) - .block(); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(OAuth2RefreshTokenGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN); - assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken()); - assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken()); - } - - @Test - public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() { - this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); - testClientCredentialsGrant(); - } - - @Test - public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - testClientCredentialsGrant(); - } - - private void testClientCredentialsGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github") - .block(); - assertThat(clientRegistration).isNotNull(); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(OAuth2ClientCredentialsGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS); - } - - @Test - public void authorizeWhenPasswordAccessTokenResponseClientBeanThenUsed() { - this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); - testPasswordGrant(); - } - - @Test - public void authorizeWhenPasswordAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - testPasswordGrant(); - } - - private void testPasswordGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2PasswordGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER"); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook") - .block(); - assertThat(clientRegistration).isNotNull(); - MockServerHttpRequest request = MockServerHttpRequest.post("/") - .contentType(MediaType.APPLICATION_FORM_URLENCODED) - .body("username=user&password=password"); - this.exchange = MockServerWebExchange.builder(request).build(); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(OAuth2PasswordGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - OAuth2PasswordGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD); - assertThat(grantRequest.getUsername()).isEqualTo("user"); - assertThat(grantRequest.getPassword()).isEqualTo("password"); - } - - @Test - public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() { - this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); - testJwtBearerGrant(); - } - - @Test - public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - testJwtBearerGrant(); - } - - private void testJwtBearerGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - - JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta").block(); - assertThat(clientRegistration).isNotNull(); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER); - assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user"); - } - - @Test - public void authorizeWhenTokenExchangeAccessTokenResponseClientBeanThenUsed() { - this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); - testTokenExchangeGrant(); - } - - @Test - public void authorizeWhenTokenExchangeAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - testTokenExchangeGrant(); - } - - private void testTokenExchangeGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - - JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("auth0").block(); - assertThat(clientRegistration).isNotNull(); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(grantRequest.getSubjectToken()).isEqualTo(authentication.getToken()); - } - - private static OAuth2AccessToken getExpiredAccessToken() { - Instant expiresAt = Instant.now().minusSeconds(60); - Instant issuedAt = expiresAt.minus(Duration.ofDays(1)); - return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt, - new HashSet<>(Arrays.asList("read", "write"))); - } - - private static Jwt getJwt() { - Instant issuedAt = Instant.now(); - return new Jwt("token", issuedAt, issuedAt.plusSeconds(300), - Collections.singletonMap(JoseHeaderNames.ALG, "RS256"), - Collections.singletonMap(JwtClaimNames.SUB, "user")); - } - - @Configuration - @EnableWebFluxSecurity - static class MinimalOAuth2ClientConfig extends OAuth2ClientBaseConfig { - - @Bean - ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { - return new WebSessionServerOAuth2AuthorizedClientRepository(); - } - - } - - @Configuration - @EnableWebFluxSecurity - static class CustomAuthorizedClientServiceConfig extends OAuth2ClientBaseConfig { - - @Bean - ReactiveOAuth2AuthorizedClientService authorizedClientService() { - ReactiveOAuth2AuthorizedClientService authorizedClientService = mock( - ReactiveOAuth2AuthorizedClientService.class); - given(authorizedClientService.loadAuthorizedClient(anyString(), anyString())).willReturn(Mono.empty()); - return authorizedClientService; - } - - } - - @Configuration - @EnableWebFluxSecurity - static class CustomAccessTokenResponseClientsConfig extends MinimalOAuth2ClientConfig { - - @Bean - ReactiveOAuth2AccessTokenResponseClient authorizationCodeAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient refreshTokenTokenAccessResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient clientCredentialsAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient passwordAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient jwtBearerAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient tokenExchangeAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - } - - @Configuration - @EnableWebFluxSecurity - static class CustomAuthorizedClientProvidersConfig extends MinimalOAuth2ClientConfig { - - @Bean - AuthorizationCodeReactiveOAuth2AuthorizedClientProvider authorizationCode() { - return spy(new AuthorizationCodeReactiveOAuth2AuthorizedClientProvider()); - } - - @Bean - RefreshTokenReactiveOAuth2AuthorizedClientProvider refreshToken() { - RefreshTokenReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenReactiveOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); - return authorizedClientProvider; - } - - @Bean - ClientCredentialsReactiveOAuth2AuthorizedClientProvider clientCredentials() { - ClientCredentialsReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsReactiveOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); - return authorizedClientProvider; - } - - @Bean - PasswordReactiveOAuth2AuthorizedClientProvider password() { - PasswordReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordReactiveOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); - return authorizedClientProvider; - } - - @Bean - JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearer() { - JwtBearerReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerReactiveOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); - return authorizedClientProvider; - } - - @Bean - TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchange() { - TokenExchangeReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); - return authorizedClientProvider; - } - - } - - abstract static class OAuth2ClientBaseConfig { - - @Bean - ReactiveClientRegistrationRepository clientRegistrationRepository() { - // @formatter:off - return new InMemoryReactiveClientRegistrationRepository( - CommonOAuth2Provider.GOOGLE.getBuilder("google") - .clientId("google-client-id") - .clientSecret("google-client-secret") - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .build(), - CommonOAuth2Provider.GITHUB.getBuilder("github") - .clientId("github-client-id") - .clientSecret("github-client-secret") - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .build(), - CommonOAuth2Provider.FACEBOOK.getBuilder("facebook") - .clientId("facebook-client-id") - .clientSecret("facebook-client-secret") - .authorizationGrantType(AuthorizationGrantType.PASSWORD) - .build(), - CommonOAuth2Provider.OKTA.getBuilder("okta") - .clientId("okta-client-id") - .clientSecret("okta-client-secret") - .authorizationGrantType(AuthorizationGrantType.JWT_BEARER) - .build(), - ClientRegistration.withRegistrationId("auth0") - .clientName("Auth0") - .clientId("auth0-client-id") - .clientSecret("auth0-client-secret") - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("user.read", "user.write") - .build()); - // @formatter:on - } - - @Bean - Consumer authorizedClientManagerConsumer() { - return (authorizedClientManager) -> authorizedClientManager - .setContextAttributesMapper((authorizeRequest) -> { - ServerWebExchange exchange = Objects - .requireNonNull(authorizeRequest.getAttribute(ServerWebExchange.class.getName())); - return exchange.getFormData().map((parameters) -> { - String username = parameters.getFirst(OAuth2ParameterNames.USERNAME); - String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD); - - Map attributes = Collections.emptyMap(); - if (StringUtils.hasText(username) && StringUtils.hasText(password)) { - attributes = new HashMap<>(); - attributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username); - attributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password); - } - - return attributes; - }); - }); - - } - - } - - private static class MockAccessTokenResponseClient - implements ReactiveOAuth2AccessTokenResponseClient { - - @Override - public Mono getTokenResponse(T grantRequest) { - return MOCK_RESPONSE_CLIENT.getTokenResponse(grantRequest); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java index ec3a71999cb..5e6d4080dd5 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java @@ -16,39 +16,16 @@ package org.springframework.security.config.annotation.web.reactive; -import java.net.URI; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.password.CompromisedPasswordCheckResult; -import org.springframework.security.authentication.password.CompromisedPasswordException; -import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker; -import org.springframework.security.config.Customizer; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.server.DefaultServerRedirectStrategy; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; /** * Tests for {@link ServerHttpSecurityConfiguration}. @@ -60,16 +37,6 @@ public class ServerHttpSecurityConfigurationTests { public final SpringTestContext spring = new SpringTestContext(this); - WebTestClient webClient; - - @Autowired - void setup(ApplicationContext context) { - if (!context.containsBean(WebHttpHandlerBuilder.WEB_HANDLER_BEAN_NAME)) { - return; - } - this.webClient = WebTestClient.bindToApplicationContext(context).configureClient().build(); - } - @Test public void loadConfigWhenReactiveUserDetailsServiceConfiguredThenServerHttpSecurityExists() { this.spring @@ -90,151 +57,9 @@ public void loadConfigWhenProxyingEnabledAndSubclassThenServerHttpSecurityExists assertThat(serverHttpSecurity).isNotNull(); } - @Test - void loginWhenCompromisePasswordCheckerConfiguredAndPasswordCompromisedThenUnauthorized() { - this.spring.register(FormLoginConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class) - .autowire(); - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - // @formatter:off - this.webClient.mutateWith(csrf()) - .post() - .uri("/login") - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().location("/login?error"); - // @formatter:on - } - - @Test - void loginWhenCompromisePasswordCheckerConfiguredAndPasswordNotCompromisedThenUnauthorized() { - this.spring.register(FormLoginConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class) - .autowire(); - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "admin"); - data.add("password", "password2"); - // @formatter:off - this.webClient.mutateWith(csrf()) - .post() - .uri("/login") - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().location("/"); - // @formatter:on - } - - @Test - void loginWhenCompromisedPasswordAndRedirectIfPasswordExceptionThenRedirectedToResetPassword() { - this.spring - .register(FormLoginRedirectToResetPasswordConfig.class, UserDetailsConfig.class, - CompromisedPasswordCheckerConfig.class) - .autowire(); - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - // @formatter:off - this.webClient.mutateWith(csrf()) - .post() - .uri("/login") - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().location("/reset-password"); - // @formatter:on - } - @Configuration static class SubclassConfig extends ServerHttpSecurityConfiguration { } - @Configuration(proxyBeanMethods = false) - @EnableWebFlux - @EnableWebFluxSecurity - static class FormLoginConfig { - - @Bean - SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((exchange) -> exchange - .anyExchange().authenticated() - ) - .formLogin(Customizer.withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebFlux - @EnableWebFluxSecurity - static class FormLoginRedirectToResetPasswordConfig { - - @Bean - SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((exchange) -> exchange - .anyExchange().authenticated() - ) - .formLogin((form) -> form - .authenticationFailureHandler((webFilterExchange, exception) -> { - String redirectUrl = "/login?error"; - if (exception instanceof CompromisedPasswordException) { - redirectUrl = "/reset-password"; - } - return new DefaultServerRedirectStrategy().sendRedirect(webFilterExchange.getExchange(), URI.create(redirectUrl)); - }) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class UserDetailsConfig { - - @Bean - MapReactiveUserDetailsService userDetailsService() { - // @formatter:off - UserDetails user = PasswordEncodedUser.user(); - UserDetails admin = User.withDefaultPasswordEncoder() - .username("admin") - .password("password2") - .roles("USER", "ADMIN") - .build(); - // @formatter:on - return new MapReactiveUserDetailsService(user, admin); - } - - } - - @Configuration(proxyBeanMethods = false) - static class CompromisedPasswordCheckerConfig { - - @Bean - TestReactivePasswordChecker compromisedPasswordChecker() { - return new TestReactivePasswordChecker(); - } - - } - - static class TestReactivePasswordChecker implements ReactiveCompromisedPasswordChecker { - - @Override - public Mono check(String password) { - if ("password".equals(password)) { - return Mono.just(new CompromisedPasswordCheckResult(true)); - } - return Mono.just(new CompromisedPasswordCheckResult(false)); - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java b/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java index 2ac71e58266..935c4e0cade 100644 --- a/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java +++ b/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java @@ -65,7 +65,7 @@ public class XsdDocumentedTests { String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd"; - String schemaDocumentLocation = "org/springframework/security/config/spring-security-6.3.xsd"; + String schemaDocumentLocation = "org/springframework/security/config/spring-security-6.2.xsd"; XmlSupport xml = new XmlSupport(); @@ -151,8 +151,8 @@ public void sizeWhenReadingFilesystemThenIsCorrectNumberOfSchemaFiles() throws I .list((dir, name) -> name.endsWith(".xsd")); // @formatter:on assertThat(schemas.length) - .withFailMessage("the count is equal to 25, if not then schemaDocument needs updating") - .isEqualTo(25); + .withFailMessage("the count is equal to 24, if not then schemaDocument needs updating") + .isEqualTo(24); } /** diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java index adeb9d8cb07..09c1c2dacd5 100644 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java +++ b/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -49,20 +49,18 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest; import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.OAuth2Error; @@ -318,47 +316,6 @@ private void testJwtBearerGrant() { assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user"); } - @Test - public void authorizeWhenTokenExchangeAccessTokenResponseClientBeanThenUsed() { - this.spring.configLocations(xml("clients")).autowire(); - testTokenExchangeGrant(); - } - - @Test - public void authorizeWhenTokenExchangeAuthorizedClientProviderBeanThenUsed() { - this.spring.configLocations(xml("providers")).autowire(); - testTokenExchangeGrant(); - } - - private void testTokenExchangeGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(accessTokenResponse); - - JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("auth0"); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(HttpServletRequest.class.getName(), this.request) - .attribute(HttpServletResponse.class.getName(), this.response) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(grantRequest.getSubjectToken()).isEqualTo(authentication.getToken()); - } - private static OAuth2AccessToken getExpiredAccessToken() { Instant expiresAt = Instant.now().minusSeconds(60); Instant issuedAt = expiresAt.minus(Duration.ofDays(1)); @@ -399,14 +356,6 @@ public static List getClientRegistrations() { .clientId("okta-client-id") .clientSecret("okta-client-secret") .authorizationGrantType(AuthorizationGrantType.JWT_BEARER) - .build(), - ClientRegistration.withRegistrationId("auth0") - .clientName("Auth0") - .clientId("auth0-client-id") - .clientSecret("auth0-client-secret") - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("user.read", "user.write") .build()); // @formatter:on } @@ -429,65 +378,95 @@ public static Consumer authorizedClientMan }); } - public static AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCode() { + public static AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider() { return spy(new AuthorizationCodeOAuth2AuthorizedClientProvider()); } - public static RefreshTokenOAuth2AuthorizedClientProvider refreshToken() { + public static RefreshTokenOAuth2AuthorizedClientProvider refreshTokenAuthorizedClientProvider() { RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(refreshTokenAccessTokenResponseClient()); return authorizedClientProvider; } - public static OAuth2AccessTokenResponseClient refreshTokenAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + public static MockRefreshTokenClient refreshTokenAccessTokenResponseClient() { + return new MockRefreshTokenClient(); } - public static ClientCredentialsOAuth2AuthorizedClientProvider clientCredentials() { + public static ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsAuthorizedClientProvider() { ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(clientCredentialsAccessTokenResponseClient()); return authorizedClientProvider; } public static OAuth2AccessTokenResponseClient clientCredentialsAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + return new MockClientCredentialsClient(); } - public static PasswordOAuth2AuthorizedClientProvider password() { + public static PasswordOAuth2AuthorizedClientProvider passwordAuthorizedClientProvider() { PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(passwordAccessTokenResponseClient()); return authorizedClientProvider; } public static OAuth2AccessTokenResponseClient passwordAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + return new MockPasswordClient(); } - public static JwtBearerOAuth2AuthorizedClientProvider jwtBearer() { + public static JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider() { JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient()); return authorizedClientProvider; } public static OAuth2AccessTokenResponseClient jwtBearerAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + return new MockJwtBearerClient(); } - public static TokenExchangeOAuth2AuthorizedClientProvider tokenExchange() { - TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient()); - return authorizedClientProvider; + private static class MockAuthorizationCodeClient + implements OAuth2AccessTokenResponseClient { + + @Override + public OAuth2AccessTokenResponse getTokenResponse( + OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) { + return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); + } + } - public static OAuth2AccessTokenResponseClient tokenExchangeAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); + private static class MockRefreshTokenClient + implements OAuth2AccessTokenResponseClient { + + @Override + public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest authorizationGrantRequest) { + return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); + } + + } + + private static class MockClientCredentialsClient + implements OAuth2AccessTokenResponseClient { + + @Override + public OAuth2AccessTokenResponse getTokenResponse( + OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) { + return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); + } + + } + + private static class MockPasswordClient implements OAuth2AccessTokenResponseClient { + + @Override + public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest authorizationGrantRequest) { + return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); + } + } - private static class MockAccessTokenResponseClient - implements OAuth2AccessTokenResponseClient { + private static class MockJwtBearerClient implements OAuth2AccessTokenResponseClient { @Override - public OAuth2AccessTokenResponse getTokenResponse(T authorizationGrantRequest) { + public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest authorizationGrantRequest) { return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); } diff --git a/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java index 5a332582d30..0d89c36370e 100644 --- a/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java @@ -16,7 +16,6 @@ package org.springframework.security.config.saml2; -import jakarta.servlet.http.HttpServletRequest; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; @@ -24,21 +23,16 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; /** * Tests for {@link RelyingPartyRegistrationsBeanDefinitionParser}. @@ -124,7 +118,6 @@ public class RelyingPartyRegistrationsBeanDefinitionParserTests { // @formatter:on @Autowired - @Qualifier("registrations") private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; public final SpringTestContext spring = new SpringTestContext(this); @@ -275,19 +268,6 @@ public void parseWhenMultiRelyingPartyRegistrationThenAvailableInRepository() { "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"); } - @Test - public void parseWhenRelayStateResolverThenUses() { - this.spring.configLocations(xml("RelayStateResolver")).autowire(); - Converter relayStateResolver = this.spring.getContext().getBean(Converter.class); - OpenSaml4AuthenticationRequestResolver authenticationRequestResolver = this.spring.getContext() - .getBean(OpenSaml4AuthenticationRequestResolver.class); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRequestURI("/saml2/authenticate/one"); - request.setServletPath("/saml2/authenticate/one"); - authenticationRequestResolver.resolve(request); - verify(relayStateResolver).convert(request); - } - private static MockResponse xmlResponse(String xml) { return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).setBody(xml); } diff --git a/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java b/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java index fe3a0b4c2f3..b8bcb40a67d 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java @@ -56,11 +56,9 @@ import org.springframework.security.web.server.ServerRedirectStrategy; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilterTests; -import org.springframework.security.web.server.authentication.DelegatingServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint; import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; -import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter; import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler; import org.springframework.security.web.server.authentication.logout.LogoutWebFilter; @@ -594,7 +592,6 @@ public void postWhenServerXorCsrfTokenRequestAttributeHandlerThenOk() { } @Test - @SuppressWarnings("unchecked") public void shouldConfigureRequestCacheForOAuth2LoginAuthenticationEntryPointAndSuccessHandler() { ServerRequestCache requestCache = spy(new WebSessionServerRequestCache()); ReactiveClientRegistrationRepository clientRegistrationRepository = mock( @@ -616,11 +613,8 @@ public void shouldConfigureRequestCacheForOAuth2LoginAuthenticationEntryPointAnd OAuth2LoginAuthenticationWebFilter authenticationWebFilter = getWebFilter(securityFilterChain, OAuth2LoginAuthenticationWebFilter.class) .get(); - DelegatingServerAuthenticationSuccessHandler handler = (DelegatingServerAuthenticationSuccessHandler) ReflectionTestUtils - .getField(authenticationWebFilter, "authenticationSuccessHandler"); - List delegates = (List) ReflectionTestUtils - .getField(handler, "delegates"); - assertThat(ReflectionTestUtils.getField(delegates.get(0), "requestCache")).isSameAs(requestCache); + Object handler = ReflectionTestUtils.getField(authenticationWebFilter, "authenticationSuccessHandler"); + assertThat(ReflectionTestUtils.getField(handler, "requestCache")).isSameAs(requestCache); } @Test diff --git a/config/src/test/java/org/springframework/security/config/web/server/SessionManagementSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/SessionManagementSpecTests.java deleted file mode 100644 index 1a380bb6e3f..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/SessionManagementSpecTests.java +++ /dev/null @@ -1,587 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.web.server; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseCookie; -import org.springframework.security.authentication.ReactiveAuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; -import org.springframework.security.core.session.InMemoryReactiveSessionRegistry; -import org.springframework.security.core.session.ReactiveSessionRegistry; -import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken; -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.oauth2.core.user.TestOAuth2Users; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler; -import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler; -import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; -import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; -import org.springframework.security.web.server.authentication.SessionLimit; -import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; -import org.springframework.web.server.session.DefaultWebSessionManager; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; - -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -public class SessionManagementSpecTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - WebTestClient client; - - @Autowired - public void setApplicationContext(ApplicationContext context) { - this.client = WebTestClient.bindToApplicationContext(context).build(); - } - - @Test - void loginWhenMaxSessionPreventsLoginThenSecondLoginFails() { - this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginConfig.class).autowire(); - - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - - ResponseCookie firstLoginSessionCookie = loginReturningCookie(data); - - // second login should fail - ResponseCookie secondLoginSessionCookie = this.client.mutateWith(csrf()) - .post() - .uri("/login") - .contentType(MediaType.MULTIPART_FORM_DATA) - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectHeader() - .location("/login?error") - .returnResult(Void.class) - .getResponseCookies() - .getFirst("SESSION"); - - assertThat(secondLoginSessionCookie).isNull(); - - // first login should still be valid - this.client.mutateWith(csrf()) - .get() - .uri("/") - .cookie(firstLoginSessionCookie.getName(), firstLoginSessionCookie.getValue()) - .exchange() - .expectStatus() - .isOk(); - } - - @Test - void httpBasicWhenUsingSavingAuthenticationInWebSessionAndPreventLoginThenSecondRequestFails() { - this.spring.register(ConcurrentSessionsHttpBasicWithWebSessionMaxSessionPreventsLoginConfig.class).autowire(); - - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - - // first request be successful - ResponseCookie sessionCookie = this.client.get() - .uri("/") - .headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange() - .expectStatus() - .isOk() - .expectCookie() - .exists("SESSION") - .returnResult(Void.class) - .getResponseCookies() - .getFirst("SESSION"); - - // request with no session should fail - this.client.get() - .uri("/") - .headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange() - .expectStatus() - .isUnauthorized(); - - // request with session obtained from first request should be successful - this.client.get() - .uri("/") - .headers((headers) -> headers.setBasicAuth("user", "password")) - .cookie(sessionCookie.getName(), sessionCookie.getValue()) - .exchange() - .expectStatus() - .isOk(); - } - - @Test - void loginWhenMaxSessionPerAuthenticationThenUserLoginFailsAndAdminLoginSucceeds() { - ConcurrentSessionsMaxSessionPreventsLoginConfig.sessionLimit = (authentication) -> { - if (authentication.getName().equals("admin")) { - return Mono.empty(); - } - return Mono.just(1); - }; - this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginConfig.class).autowire(); - - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - MultiValueMap adminCreds = new LinkedMultiValueMap<>(); - adminCreds.add("username", "admin"); - adminCreds.add("password", "password"); - - ResponseCookie userFirstLoginSessionCookie = loginReturningCookie(data); - ResponseCookie adminFirstLoginSessionCookie = loginReturningCookie(adminCreds); - // second user login should fail - this.client.mutateWith(csrf()) - .post() - .uri("/login") - .contentType(MediaType.MULTIPART_FORM_DATA) - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectHeader() - .location("/login?error"); - // first login should still be valid - this.client.mutateWith(csrf()) - .get() - .uri("/") - .cookie(userFirstLoginSessionCookie.getName(), userFirstLoginSessionCookie.getValue()) - .exchange() - .expectStatus() - .isOk(); - ResponseCookie adminSecondLoginSessionCookie = loginReturningCookie(adminCreds); - this.client.mutateWith(csrf()) - .get() - .uri("/") - .cookie(adminFirstLoginSessionCookie.getName(), adminFirstLoginSessionCookie.getValue()) - .exchange() - .expectStatus() - .isOk(); - this.client.mutateWith(csrf()) - .get() - .uri("/") - .cookie(adminSecondLoginSessionCookie.getName(), adminSecondLoginSessionCookie.getValue()) - .exchange() - .expectStatus() - .isOk(); - } - - @Test - void loginWhenMaxSessionDoesNotPreventLoginThenSecondLoginSucceedsAndFirstSessionIsInvalidated() { - ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.sessionLimit = SessionLimit.of(1); - this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.class).autowire(); - - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - - ResponseCookie firstLoginSessionCookie = loginReturningCookie(data); - ResponseCookie secondLoginSessionCookie = loginReturningCookie(data); - - // first login should not be valid - this.client.get() - .uri("/") - .cookie(firstLoginSessionCookie.getName(), firstLoginSessionCookie.getValue()) - .exchange() - .expectStatus() - .isFound() - .expectHeader() - .location("/login"); - - // second login should be valid - this.client.get() - .uri("/") - .cookie(secondLoginSessionCookie.getName(), secondLoginSessionCookie.getValue()) - .exchange() - .expectStatus() - .isOk(); - } - - @Test - void loginWhenMaxSessionDoesNotPreventLoginThenLeastRecentlyUsedSessionIsInvalidated() { - ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.sessionLimit = SessionLimit.of(2); - this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.class).autowire(); - - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - - ResponseCookie firstLoginSessionCookie = loginReturningCookie(data); - ResponseCookie secondLoginSessionCookie = loginReturningCookie(data); - - // update last access time for first request - this.client.get() - .uri("/") - .cookie(firstLoginSessionCookie.getName(), firstLoginSessionCookie.getValue()) - .exchange() - .expectStatus() - .isOk(); - - ResponseCookie thirdLoginSessionCookie = loginReturningCookie(data); - - // second login should be invalid, it is the least recently used session - this.client.get() - .uri("/") - .cookie(secondLoginSessionCookie.getName(), secondLoginSessionCookie.getValue()) - .exchange() - .expectStatus() - .isFound() - .expectHeader() - .location("/login"); - - // first login should be valid - this.client.get() - .uri("/") - .cookie(firstLoginSessionCookie.getName(), firstLoginSessionCookie.getValue()) - .exchange() - .expectStatus() - .isOk(); - - // third login should be valid - this.client.get() - .uri("/") - .cookie(thirdLoginSessionCookie.getName(), thirdLoginSessionCookie.getValue()) - .exchange() - .expectStatus() - .isOk(); - } - - @Test - void oauth2LoginWhenMaxSessionsThenPreventLogin() { - OAuth2LoginConcurrentSessionsConfig.maxSessions = 1; - OAuth2LoginConcurrentSessionsConfig.preventLogin = true; - this.spring.register(OAuth2LoginConcurrentSessionsConfig.class).autowire(); - prepareOAuth2Config(); - // @formatter:off - ResponseCookie sessionCookie = this.client.get() - .uri("/login/oauth2/code/client-credentials") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", "/") - .expectCookie().exists("SESSION") - .returnResult(Void.class) - .getResponseCookies() - .getFirst("SESSION"); - - this.client.get() - .uri("/login/oauth2/code/client-credentials") - .exchange() - .expectHeader().location("/login?error"); - - this.client.get().uri("/") - .cookie(sessionCookie.getName(), sessionCookie.getValue()) - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("ok"); - // @formatter:on - } - - @Test - void oauth2LoginWhenMaxSessionDoesNotPreventLoginThenSecondLoginSucceedsAndFirstSessionIsInvalidated() { - OAuth2LoginConcurrentSessionsConfig.maxSessions = 1; - OAuth2LoginConcurrentSessionsConfig.preventLogin = false; - this.spring.register(OAuth2LoginConcurrentSessionsConfig.class).autowire(); - prepareOAuth2Config(); - // @formatter:off - ResponseCookie firstLoginCookie = this.client.get() - .uri("/login/oauth2/code/client-credentials") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", "/") - .expectCookie().exists("SESSION") - .returnResult(Void.class) - .getResponseCookies() - .getFirst("SESSION"); - ResponseCookie secondLoginCookie = this.client.get() - .uri("/login/oauth2/code/client-credentials") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", "/") - .expectCookie().exists("SESSION") - .returnResult(Void.class) - .getResponseCookies() - .getFirst("SESSION"); - - this.client.get().uri("/") - .cookie(firstLoginCookie.getName(), firstLoginCookie.getValue()) - .exchange() - .expectStatus().isFound() - .expectHeader().location("/login"); - - this.client.get().uri("/") - .cookie(secondLoginCookie.getName(), secondLoginCookie.getValue()) - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("ok"); - // @formatter:on - } - - @Test - void loginWhenAuthenticationSuccessHandlerOverriddenThenConcurrentSessionHandlersBackOff() { - this.spring.register(ConcurrentSessionsFormLoginOverrideAuthenticationSuccessHandlerConfig.class).autowire(); - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - // first login should be successful - login(data).expectStatus().isFound().expectHeader().location("/"); - // second login should be successful, there should be no concurrent session - // control - login(data).expectStatus().isFound().expectHeader().location("/"); - } - - private void prepareOAuth2Config() { - OAuth2LoginConcurrentSessionsConfig config = this.spring.getContext() - .getBean(OAuth2LoginConcurrentSessionsConfig.class); - ServerAuthenticationConverter converter = config.authenticationConverter; - ReactiveAuthenticationManager manager = config.manager; - ServerOAuth2AuthorizationRequestResolver resolver = config.resolver; - OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success(); - OAuth2User user = TestOAuth2Users.create(); - OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes(); - OAuth2LoginAuthenticationToken result = new OAuth2LoginAuthenticationToken( - TestClientRegistrations.clientRegistration().build(), exchange, user, user.getAuthorities(), - accessToken); - given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); - given(manager.authenticate(any())).willReturn(Mono.just(result)); - given(resolver.resolve(any())).willReturn(Mono.empty()); - } - - private ResponseCookie loginReturningCookie(MultiValueMap data) { - return login(data).expectCookie() - .exists("SESSION") - .returnResult(Void.class) - .getResponseCookies() - .getFirst("SESSION"); - } - - private WebTestClient.ResponseSpec login(MultiValueMap data) { - return this.client.mutateWith(csrf()) - .post() - .uri("/login") - .contentType(MediaType.MULTIPART_FORM_DATA) - .body(BodyInserters.fromFormData(data)) - .exchange(); - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - @Import(Config.class) - static class ConcurrentSessionsMaxSessionPreventsLoginConfig { - - static SessionLimit sessionLimit = SessionLimit.of(1); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated()) - .formLogin(Customizer.withDefaults()) - .sessionManagement((sessionManagement) -> sessionManagement - .concurrentSessions((concurrentSessions) -> concurrentSessions - .maximumSessions(sessionLimit) - .maximumSessionsExceededHandler(new PreventLoginServerMaximumSessionsExceededHandler()) - ) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - @Import(Config.class) - static class OAuth2LoginConcurrentSessionsConfig { - - static int maxSessions = 1; - - static boolean preventLogin = true; - - ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class); - - ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); - - ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class); - - @Bean - SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http, - DefaultWebSessionManager webSessionManager) { - // @formatter:off - http - .authorizeExchange((exchanges) -> exchanges - .anyExchange().authenticated() - ) - .oauth2Login((oauth2Login) -> oauth2Login - .authenticationConverter(this.authenticationConverter) - .authenticationManager(this.manager) - .authorizationRequestResolver(this.resolver) - ) - .sessionManagement((sessionManagement) -> sessionManagement - .concurrentSessions((concurrentSessions) -> concurrentSessions - .maximumSessions(SessionLimit.of(maxSessions)) - .maximumSessionsExceededHandler(preventLogin - ? new PreventLoginServerMaximumSessionsExceededHandler() - : new InvalidateLeastUsedServerMaximumSessionsExceededHandler(webSessionManager.getSessionStore())) - ) - ); - // @formatter:on - return http.build(); - } - - @Bean - InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { - return new InMemoryReactiveClientRegistrationRepository( - TestClientRegistrations.clientCredentials().build()); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - @Import(Config.class) - static class ConcurrentSessionsMaxSessionPreventsLoginFalseConfig { - - static SessionLimit sessionLimit = SessionLimit.of(1); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated()) - .formLogin(Customizer.withDefaults()) - .sessionManagement((sessionManagement) -> sessionManagement - .concurrentSessions((concurrentSessions) -> concurrentSessions - .maximumSessions(sessionLimit) - ) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - @Import(Config.class) - static class ConcurrentSessionsFormLoginOverrideAuthenticationSuccessHandlerConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated()) - .formLogin((login) -> login - .authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/")) - ) - .sessionManagement((sessionManagement) -> sessionManagement - .concurrentSessions((concurrentSessions) -> concurrentSessions - .maximumSessions(SessionLimit.of(1)) - .maximumSessionsExceededHandler(new PreventLoginServerMaximumSessionsExceededHandler()) - ) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - @Import(Config.class) - static class ConcurrentSessionsHttpBasicWithWebSessionMaxSessionPreventsLoginConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated()) - .httpBasic((basic) -> basic - .securityContextRepository(new WebSessionServerSecurityContextRepository()) - ) - .sessionManagement((sessionManagement) -> sessionManagement - .concurrentSessions((concurrentSessions) -> concurrentSessions - .maximumSessions(SessionLimit.of(1)) - .maximumSessionsExceededHandler(new PreventLoginServerMaximumSessionsExceededHandler()) - ) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @Import({ ReactiveAuthenticationTestConfiguration.class, DefaultController.class }) - static class Config { - - @Bean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME) - DefaultWebSessionManager webSessionManager() { - return new DefaultWebSessionManager(); - } - - @Bean - ReactiveSessionRegistry reactiveSessionRegistry() { - return new InMemoryReactiveSessionRegistry(); - } - - } - - @RestController - static class DefaultController { - - @GetMapping("/") - String index() { - return "ok"; - } - - } - -} diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt index 0af6bbb4833..e3419213251 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt @@ -816,41 +816,4 @@ class AuthorizeHttpRequestsDslTests { } } - - @Test - fun `request when ip address does not match then responds with forbidden`() { - this.spring.register(HasIpAddressConfig::class.java).autowire() - - this.mockMvc.perform(get("/path") - .with { request -> - request.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error") - request.apply { - dispatcherType = DispatcherType.ERROR - } - }) - .andExpect(status().isForbidden) - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - open class HasIpAddressConfig { - - @Bean - open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - http { - authorizeHttpRequests { - authorize(anyRequest, hasIpAddress("10.0.0.0/24")) - } - } - return http.build() - } - - @RestController - internal class PathController { - @RequestMapping("/path") - fun path() { - } - } - } } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt index 965c361b4a3..8c44ef8524a 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/FormLoginDslTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -33,7 +33,6 @@ import org.springframework.security.config.test.SpringTestContextExtension import org.springframework.security.core.userdetails.User import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf -import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler @@ -368,50 +367,6 @@ class FormLoginDslTests { verify(exactly = 1) { CustomAuthenticationDetailsSourceConfig.AUTHENTICATION_DETAILS_SOURCE.buildDetails(any()) } } - @Configuration - @EnableWebSecurity - open class CustomUsernameParameterConfig { - @Bean - open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - http { - formLogin { - usernameParameter = "custom-username" - } - } - return http.build() - } - } - - @Test - fun `form login when custom username parameter then used`() { - this.spring.register(CustomUsernameParameterConfig::class.java, UserConfig::class.java).autowire() - - this.mockMvc.perform(formLogin().userParameter("custom-username")) - .andExpect(authenticated()) - } - - @Configuration - @EnableWebSecurity - open class CustomPasswordParameterConfig { - @Bean - open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - http { - formLogin { - passwordParameter = "custom-password" - } - } - return http.build() - } - } - - @Test - fun `form login when custom password parameter then used`() { - this.spring.register(CustomPasswordParameterConfig::class.java, UserConfig::class.java).autowire() - - this.mockMvc.perform(formLogin().passwordParam("custom-password")) - .andExpect(authenticated()) - } - @Configuration @EnableWebSecurity open class CustomAuthenticationDetailsSourceConfig { diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/Saml2LogoutDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/Saml2LogoutDslTests.kt deleted file mode 100644 index 9e3e8fceca9..00000000000 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/Saml2LogoutDslTests.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.web - -import org.assertj.core.api.Assertions -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.springframework.beans.factory.BeanCreationException -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.security.authentication.TestAuthentication -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity -import org.springframework.security.config.test.SpringTestContext -import org.springframework.security.config.test.SpringTestContextExtension -import org.springframework.security.core.authority.AuthorityUtils -import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication -import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors -import org.springframework.security.web.SecurityFilterChain -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.MvcResult -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders -import org.springframework.test.web.servlet.result.MockMvcResultMatchers -import java.util.* - -/** - * Tests for [Saml2LogoutDsl] - * - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension::class) -class Saml2LogoutDslTests { - @JvmField - val spring = SpringTestContext(this) - - @Autowired - lateinit var mockMvc: MockMvc - - @Test - fun `saml2Logout when no relying party registration repository then exception`() { - Assertions.assertThatThrownBy { this.spring.register(Saml2LogoutNoRelyingPartyRegistrationRepoConfig::class.java).autowire() } - .isInstanceOf(BeanCreationException::class.java) - .hasMessageContaining("relyingPartyRegistrationRepository cannot be null") - - } - - @Test - @Throws(Exception::class) - fun `saml2Logout when defaults and not saml login then default logout`() { - this.spring.register(Saml2LogoutDefaultsConfig::class.java).autowire() - val user = TestAuthentication.authenticatedUser() - val result: MvcResult = this.mockMvc.perform( - MockMvcRequestBuilders.post("/logout").with(SecurityMockMvcRequestPostProcessors.authentication(user)) - .with(SecurityMockMvcRequestPostProcessors.csrf())) - .andExpect(MockMvcResultMatchers.status().isFound()) - .andReturn() - val location = result.response.getHeader("Location") - Assertions.assertThat(location).isEqualTo("/login?logout") - } - - @Test - @Throws(Exception::class) - fun saml2LogoutWhenDefaultsThenLogsOutAndSendsLogoutRequest() { - this.spring.register(Saml2LogoutDefaultsConfig::class.java).autowire() - val principal = DefaultSaml2AuthenticatedPrincipal("user", emptyMap()) - principal.relyingPartyRegistrationId = "registration-id" - val user = Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")) - val result: MvcResult = this.mockMvc.perform(MockMvcRequestBuilders.post("/logout") - .with(SecurityMockMvcRequestPostProcessors.authentication(user)) - .with(SecurityMockMvcRequestPostProcessors.csrf())) - .andExpect(MockMvcResultMatchers.status().isFound()) - .andReturn() - val location = result.response.getHeader("Location") - Assertions.assertThat(location).startsWith("https://ap.example.org/logout/saml2/request") - } - - @Configuration - @EnableWebSecurity - open class Saml2LogoutNoRelyingPartyRegistrationRepoConfig { - @Bean - open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - http { - saml2Logout { } - } - return http.build() - } - } - - @Configuration - @EnableWebSecurity - open class Saml2LogoutDefaultsConfig { - - @Bean - open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - http { - saml2Logout { } - } - return http.build() - } - - @Bean - open fun registrations(): RelyingPartyRegistrationRepository = - InMemoryRelyingPartyRegistrationRepository(TestRelyingPartyRegistrations.full().build()) - - } - -} diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerSessionManagementDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerSessionManagementDslTests.kt deleted file mode 100644 index a8c23c43bc2..00000000000 --- a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerSessionManagementDslTests.kt +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.web.server - -import org.junit.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.context.ApplicationContext -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Import -import org.springframework.http.MediaType -import org.springframework.http.ResponseCookie -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity -import org.springframework.security.config.test.SpringTestContext -import org.springframework.security.config.test.SpringTestContextExtension -import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration -import org.springframework.security.core.session.InMemoryReactiveSessionRegistry -import org.springframework.security.core.session.ReactiveSessionRegistry -import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers -import org.springframework.security.web.server.SecurityWebFilterChain -import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler -import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler -import org.springframework.security.web.server.authentication.SessionLimit -import org.springframework.test.web.reactive.server.WebTestClient -import org.springframework.util.LinkedMultiValueMap -import org.springframework.util.MultiValueMap -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RestController -import org.springframework.web.reactive.config.EnableWebFlux -import org.springframework.web.reactive.function.BodyInserters -import org.springframework.web.server.adapter.WebHttpHandlerBuilder -import org.springframework.web.server.session.DefaultWebSessionManager - -/** - * Tests for [ServerSessionManagementDsl] - * - * @author Marcus da Coregio - */ -@ExtendWith(SpringTestContextExtension::class) -class ServerSessionManagementDslTests { - - @JvmField - val spring = SpringTestContext(this) - - private lateinit var client: WebTestClient - - @Autowired - fun setup(context: ApplicationContext) { - this.client = WebTestClient - .bindToApplicationContext(context) - .configureClient() - .build() - } - - @Test - fun `login when max sessions prevent login then second login fails`() { - this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginTrueConfig::class.java).autowire() - - val data: MultiValueMap = LinkedMultiValueMap() - data.add("username", "user") - data.add("password", "password") - - val firstLoginSessionCookie = loginReturningCookie(data) - - // second login should fail - this.client.mutateWith(SecurityMockServerConfigurers.csrf()) - .post() - .uri("/login") - .contentType(MediaType.MULTIPART_FORM_DATA) - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectHeader() - .location("/login?error") - - // first login should still be valid - this.client.mutateWith(SecurityMockServerConfigurers.csrf()) - .get() - .uri("/") - .cookie(firstLoginSessionCookie!!.name, firstLoginSessionCookie.value) - .exchange() - .expectStatus() - .isOk() - } - - @Test - fun `login when max sessions does not prevent login then seconds login succeeds and first session is invalidated`() { - ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.maxSessions = 1 - this.spring.register(SessionManagementSpecTests.ConcurrentSessionsMaxSessionPreventsLoginFalseConfig::class.java) - .autowire() - - val data: MultiValueMap = LinkedMultiValueMap() - data.add("username", "user") - data.add("password", "password") - - val firstLoginSessionCookie = loginReturningCookie(data) - val secondLoginSessionCookie = loginReturningCookie(data) - - // first login should not be valid - this.client.get() - .uri("/") - .cookie(firstLoginSessionCookie!!.name, firstLoginSessionCookie.value) - .exchange() - .expectStatus() - .isFound() - .expectHeader() - .location("/login") - - // second login should be valid - this.client.get() - .uri("/") - .cookie(secondLoginSessionCookie!!.name, secondLoginSessionCookie.value) - .exchange() - .expectStatus() - .isOk() - } - - @Test - fun `login when max sessions does not prevent login then least recently used session is invalidated`() { - ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.maxSessions = 2 - this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginFalseConfig::class.java).autowire() - val data: MultiValueMap = LinkedMultiValueMap() - data.add("username", "user") - data.add("password", "password") - val firstLoginSessionCookie = loginReturningCookie(data) - val secondLoginSessionCookie = loginReturningCookie(data) - - // update last access time for first request - this.client.get() - .uri("/") - .cookie(firstLoginSessionCookie!!.name, firstLoginSessionCookie.value) - .exchange() - .expectStatus() - .isOk() - val thirdLoginSessionCookie = loginReturningCookie(data) - - // second login should be invalid, it is the least recently used session - this.client.get() - .uri("/") - .cookie(secondLoginSessionCookie!!.name, secondLoginSessionCookie.value) - .exchange() - .expectStatus() - .isFound() - .expectHeader() - .location("/login") - - // first login should be valid - this.client.get() - .uri("/") - .cookie(firstLoginSessionCookie.name, firstLoginSessionCookie.value) - .exchange() - .expectStatus() - .isOk() - - // third login should be valid - this.client.get() - .uri("/") - .cookie(thirdLoginSessionCookie!!.name, thirdLoginSessionCookie.value) - .exchange() - .expectStatus() - .isOk() - } - - private fun loginReturningCookie(data: MultiValueMap): ResponseCookie? { - return login(data).expectCookie() - .exists("SESSION") - .returnResult(Void::class.java) - .responseCookies - .getFirst("SESSION") - } - - private fun login(data: MultiValueMap): WebTestClient.ResponseSpec { - return client.mutateWith(SecurityMockServerConfigurers.csrf()) - .post() - .uri("/login") - .contentType(MediaType.MULTIPART_FORM_DATA) - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectStatus() - .is3xxRedirection() - .expectHeader() - .location("/") - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - @Import(Config::class) - open class ConcurrentSessionsMaxSessionPreventsLoginFalseConfig { - - companion object { - var maxSessions = 1 - } - - @Bean - open fun springSecurity(http: ServerHttpSecurity, webSessionManager: DefaultWebSessionManager): SecurityWebFilterChain { - return http { - authorizeExchange { - authorize(anyExchange, authenticated) - } - formLogin { } - sessionManagement { - sessionConcurrency { - maximumSessions = SessionLimit.of(maxSessions) - maximumSessionsExceededHandler = InvalidateLeastUsedServerMaximumSessionsExceededHandler(webSessionManager.sessionStore) - } - } - } - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - @Import(Config::class) - open class ConcurrentSessionsMaxSessionPreventsLoginTrueConfig { - - @Bean - open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - authorizeExchange { - authorize(anyExchange, authenticated) - } - formLogin { } - sessionManagement { - sessionConcurrency { - maximumSessions = SessionLimit.of(1) - maximumSessionsExceededHandler = - PreventLoginServerMaximumSessionsExceededHandler() - } - } - } - } - - } - - @Configuration - @Import( - ReactiveAuthenticationTestConfiguration::class, - DefaultController::class - ) - open class Config { - - @Bean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME) - open fun webSessionManager(): DefaultWebSessionManager { - return DefaultWebSessionManager() - } - - @Bean - open fun reactiveSessionRegistry(): ReactiveSessionRegistry { - return InMemoryReactiveSessionRegistry() - } - - } - - @RestController - open class DefaultController { - - @GetMapping("/") - fun index(): String { - return "ok" - } - - } - - -} diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-clients.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-clients.xml index c0a9c67156c..416520c6f7b 100644 --- a/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-clients.xml +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-clients.xml @@ -53,7 +53,4 @@ - - \ No newline at end of file diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-providers.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-providers.xml index 0f167f5ed1a..1966d46371d 100644 --- a/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-providers.xml +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-providers.xml @@ -42,21 +42,18 @@ factory-method="authorizedClientManagerConsumer"/> + factory-method="authorizationCodeAuthorizedClientProvider"/> + factory-method="refreshTokenAuthorizedClientProvider"/> + factory-method="clientCredentialsAuthorizedClientProvider"/> + factory-method="passwordAuthorizedClientProvider"/> - - + factory-method="jwtBearerAuthorizedClientProvider"/> \ No newline at end of file diff --git a/config/src/test/resources/org/springframework/security/config/method-security.xml b/config/src/test/resources/org/springframework/security/config/method-security.xml index e9b6ec2c373..9551041262b 100644 --- a/config/src/test/resources/org/springframework/security/config/method-security.xml +++ b/config/src/test/resources/org/springframework/security/config/method-security.xml @@ -6,7 +6,7 @@ xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd - http://www.springframework.org/schema/security org/springframework/security/config/spring-security-6.3.xsd"> + http://www.springframework.org/schema/security org/springframework/security/config/spring-security-6.2.xsd"> diff --git a/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-MultiRegistration.xml b/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-MultiRegistration.xml index 2c3d00f46e2..dd274db7eb2 100644 --- a/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-MultiRegistration.xml +++ b/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-MultiRegistration.xml @@ -23,20 +23,20 @@ https://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> - - + + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - diff --git a/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-SingleRegistration.xml b/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-SingleRegistration.xml index e68426bfd8a..2b97e6f1c13 100644 --- a/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-SingleRegistration.xml +++ b/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-SingleRegistration.xml @@ -23,14 +23,14 @@ https://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> - - + + - + @@ -76,68 +74,30 @@ * your intentions clearer. * * @author Michael Mayr - * @author Josh Cummings */ public class RoleHierarchyImpl implements RoleHierarchy { private static final Log logger = LogFactory.getLog(RoleHierarchyImpl.class); /** - * {@code rolesReachableInOneOrMoreStepsMap} is a Map that under the key of a specific - * role name contains a set of all roles reachable from this role in 1 or more steps + * Raw hierarchy configuration where each line represents single or multiple level + * role chain. */ - private Map> rolesReachableInOneOrMoreStepsMap = null; + private String roleHierarchyStringRepresentation = null; /** - * @deprecated Use {@link RoleHierarchyImpl#fromHierarchy} instead + * {@code rolesReachableInOneStepMap} is a Map that under the key of a specific role + * name contains a set of all roles reachable from this role in 1 step (i.e. parsed + * {@link #roleHierarchyStringRepresentation} grouped by the higher role) */ - @Deprecated - public RoleHierarchyImpl() { - - } - - private RoleHierarchyImpl(Map> hierarchy) { - this.rolesReachableInOneOrMoreStepsMap = buildRolesReachableInOneOrMoreStepsMap(hierarchy); - } - - /** - * Create a role hierarchy instance with the given definition, similar to the - * following: - * - *

-	 *     ROLE_A > ROLE_B
-	 *     ROLE_B > ROLE_AUTHENTICATED
-	 *     ROLE_AUTHENTICATED > ROLE_UNAUTHENTICATED
-	 * 
- * @param hierarchy the role hierarchy to use - * @return a {@link RoleHierarchyImpl} that uses the given {@code hierarchy} - */ - public static RoleHierarchyImpl fromHierarchy(String hierarchy) { - return new RoleHierarchyImpl(buildRolesReachableInOneStepMap(hierarchy)); - } + private Map> rolesReachableInOneStepMap = null; /** - * Factory method that creates a {@link Builder} instance with the default role prefix - * "ROLE_" - * @return a {@link Builder} instance with the default role prefix "ROLE_" - * @since 6.3 - */ - public static Builder withDefaultRolePrefix() { - return withRolePrefix("ROLE_"); - } - - /** - * Factory method that creates a {@link Builder} instance with the specified role - * prefix. - * @param rolePrefix the prefix to be used for the roles in the hierarchy. - * @return a new {@link Builder} instance with the specified role prefix - * @throws IllegalArgumentException if the provided role prefix is null - * @since 6.3 + * {@code rolesReachableInOneOrMoreStepsMap} is a Map that under the key of a specific + * role name contains a set of all roles reachable from this role in 1 or more steps + * (i.e. fully resolved hierarchy from {@link #rolesReachableInOneStepMap}) */ - public static Builder withRolePrefix(String rolePrefix) { - Assert.notNull(rolePrefix, "rolePrefix must not be null"); - return new Builder(rolePrefix); - } + private Map> rolesReachableInOneOrMoreStepsMap = null; /** * Set the role hierarchy and pre-calculate for every role the set of all reachable @@ -146,15 +106,13 @@ public static Builder withRolePrefix(String rolePrefix) { * time). During pre-calculation, cycles in role hierarchy are detected and will cause * a CycleInRoleHierarchyException to be thrown. * @param roleHierarchyStringRepresentation - String definition of the role hierarchy. - * @deprecated Use {@link RoleHierarchyImpl#fromHierarchy} instead */ - @Deprecated public void setHierarchy(String roleHierarchyStringRepresentation) { + this.roleHierarchyStringRepresentation = roleHierarchyStringRepresentation; logger.debug(LogMessage.format("setHierarchy() - The following role hierarchy was set: %s", roleHierarchyStringRepresentation)); - Map> hierarchy = buildRolesReachableInOneStepMap( - roleHierarchyStringRepresentation); - this.rolesReachableInOneOrMoreStepsMap = buildRolesReachableInOneOrMoreStepsMap(hierarchy); + buildRolesReachableInOneStepMap(); + buildRolesReachableInOneOrMoreStepsMap(); } @Override @@ -198,21 +156,21 @@ public Collection getReachableGrantedAuthorities( * Parse input and build the map for the roles reachable in one step: the higher role * will become a key that references a set of the reachable lower roles. */ - private static Map> buildRolesReachableInOneStepMap(String hierarchy) { - Map> rolesReachableInOneStepMap = new HashMap<>(); - for (String line : hierarchy.split("\n")) { + private void buildRolesReachableInOneStepMap() { + this.rolesReachableInOneStepMap = new HashMap<>(); + for (String line : this.roleHierarchyStringRepresentation.split("\n")) { // Split on > and trim excessive whitespace String[] roles = line.trim().split("\\s+>\\s+"); for (int i = 1; i < roles.length; i++) { String higherRole = roles[i - 1]; GrantedAuthority lowerRole = new SimpleGrantedAuthority(roles[i]); Set rolesReachableInOneStepSet; - if (!rolesReachableInOneStepMap.containsKey(higherRole)) { + if (!this.rolesReachableInOneStepMap.containsKey(higherRole)) { rolesReachableInOneStepSet = new HashSet<>(); - rolesReachableInOneStepMap.put(higherRole, rolesReachableInOneStepSet); + this.rolesReachableInOneStepMap.put(higherRole, rolesReachableInOneStepSet); } else { - rolesReachableInOneStepSet = rolesReachableInOneStepMap.get(higherRole); + rolesReachableInOneStepSet = this.rolesReachableInOneStepMap.get(higherRole); } rolesReachableInOneStepSet.add(lowerRole); logger.debug(LogMessage.format( @@ -220,7 +178,6 @@ private static Map> buildRolesReachableInOneStepMa higherRole, lowerRole)); } } - return rolesReachableInOneStepMap; } /** @@ -229,105 +186,30 @@ private static Map> buildRolesReachableInOneStepMa * CycleInRoleHierarchyException if a cycle in the role hierarchy definition is * detected) */ - private static Map> buildRolesReachableInOneOrMoreStepsMap( - Map> hierarchy) { - Map> rolesReachableInOneOrMoreStepsMap = new HashMap<>(); + private void buildRolesReachableInOneOrMoreStepsMap() { + this.rolesReachableInOneOrMoreStepsMap = new HashMap<>(); // iterate over all higher roles from rolesReachableInOneStepMap - for (String roleName : hierarchy.keySet()) { - Set rolesToVisitSet = new HashSet<>(hierarchy.get(roleName)); + for (String roleName : this.rolesReachableInOneStepMap.keySet()) { + Set rolesToVisitSet = new HashSet<>(this.rolesReachableInOneStepMap.get(roleName)); Set visitedRolesSet = new HashSet<>(); while (!rolesToVisitSet.isEmpty()) { // take a role from the rolesToVisit set GrantedAuthority lowerRole = rolesToVisitSet.iterator().next(); rolesToVisitSet.remove(lowerRole); - if (!visitedRolesSet.add(lowerRole) || !hierarchy.containsKey(lowerRole.getAuthority())) { + if (!visitedRolesSet.add(lowerRole) + || !this.rolesReachableInOneStepMap.containsKey(lowerRole.getAuthority())) { continue; // Already visited role or role with missing hierarchy } else if (roleName.equals(lowerRole.getAuthority())) { throw new CycleInRoleHierarchyException(); } - rolesToVisitSet.addAll(hierarchy.get(lowerRole.getAuthority())); + rolesToVisitSet.addAll(this.rolesReachableInOneStepMap.get(lowerRole.getAuthority())); } - rolesReachableInOneOrMoreStepsMap.put(roleName, visitedRolesSet); + this.rolesReachableInOneOrMoreStepsMap.put(roleName, visitedRolesSet); logger.debug(LogMessage.format( "buildRolesReachableInOneOrMoreStepsMap() - From role %s one can reach %s in one or more steps.", roleName, visitedRolesSet)); } - return rolesReachableInOneOrMoreStepsMap; - } - - /** - * Builder class for constructing a {@link RoleHierarchyImpl} based on a hierarchical - * role structure. - * - * @author Federico Herrera - * @since 6.3 - */ - public static final class Builder { - - private final String rolePrefix; - - private final Map> hierarchy; - - private Builder(String rolePrefix) { - this.rolePrefix = rolePrefix; - this.hierarchy = new LinkedHashMap<>(); - } - - /** - * Creates a new hierarchy branch to define a role and its child roles. - * @param role the highest role in this branch - * @return a {@link ImpliedRoles} to define the child roles for the - * role - */ - public ImpliedRoles role(String role) { - Assert.hasText(role, "role must not be empty"); - return new ImpliedRoles(role); - } - - /** - * Builds and returns a {@link RoleHierarchyImpl} describing the defined role - * hierarchy. - * @return a {@link RoleHierarchyImpl} - */ - public RoleHierarchyImpl build() { - return new RoleHierarchyImpl(this.hierarchy); - } - - private Builder addHierarchy(String role, String... impliedRoles) { - Set withPrefix = new HashSet<>(); - for (String impliedRole : impliedRoles) { - withPrefix.add(new SimpleGrantedAuthority(this.rolePrefix.concat(impliedRole))); - } - this.hierarchy.put(this.rolePrefix.concat(role), withPrefix); - return this; - } - - /** - * Builder class for constructing child roles within a role hierarchy branch. - */ - public final class ImpliedRoles { - - private final String role; - - private ImpliedRoles(String role) { - this.role = role; - } - - /** - * Specifies implied role(s) for the current role in the hierarchy. - * @param impliedRoles role name(s) implied by the role. - * @return the same {@link Builder} instance - * @throws IllegalArgumentException if impliedRoles is null, - * empty or contains any null element. - */ - public Builder implies(String... impliedRoles) { - Assert.notEmpty(impliedRoles, "at least one implied role must be provided"); - Assert.noNullElements(impliedRoles, "implied role name(s) cannot be empty"); - return Builder.this.addHierarchy(this.role, impliedRoles); - } - - } } diff --git a/core/src/main/java/org/springframework/security/access/prepost/PostAuthorize.java b/core/src/main/java/org/springframework/security/access/prepost/PostAuthorize.java index b9847f8f774..18a60ef88f8 100644 --- a/core/src/main/java/org/springframework/security/access/prepost/PostAuthorize.java +++ b/core/src/main/java/org/springframework/security/access/prepost/PostAuthorize.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core/src/main/java/org/springframework/security/access/prepost/PreAuthorize.java b/core/src/main/java/org/springframework/security/access/prepost/PreAuthorize.java index f056eebb167..ba711030533 100644 --- a/core/src/main/java/org/springframework/security/access/prepost/PreAuthorize.java +++ b/core/src/main/java/org/springframework/security/access/prepost/PreAuthorize.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java b/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java index 8f0ebb089ed..0970b79aa50 100644 --- a/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java +++ b/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java @@ -25,10 +25,6 @@ import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; -import org.springframework.security.authentication.password.CompromisedPasswordCheckResult; -import org.springframework.security.authentication.password.CompromisedPasswordChecker; -import org.springframework.security.authentication.password.CompromisedPasswordException; -import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker; import org.springframework.security.core.Authentication; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService; @@ -68,8 +64,6 @@ public abstract class AbstractUserDetailsReactiveAuthenticationManager private UserDetailsChecker postAuthenticationChecks = this::defaultPostAuthenticationChecks; - private ReactiveCompromisedPasswordChecker compromisedPasswordChecker; - private void defaultPreAuthenticationChecks(UserDetails user) { if (!user.isAccountNonLocked()) { this.logger.debug("User account is locked"); @@ -106,23 +100,12 @@ public Mono authenticate(Authentication authentication) { .publishOn(this.scheduler) .filter((userDetails) -> this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) .switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials")))) - .flatMap((userDetails) -> checkCompromisedPassword(presentedPassword).thenReturn(userDetails)) .flatMap((userDetails) -> upgradeEncodingIfNecessary(userDetails, presentedPassword)) .doOnNext(this.postAuthenticationChecks::check) .map(this::createUsernamePasswordAuthenticationToken); // @formatter:on } - private Mono checkCompromisedPassword(String password) { - if (this.compromisedPasswordChecker == null) { - return Mono.empty(); - } - return this.compromisedPasswordChecker.check(password) - .filter(CompromisedPasswordCheckResult::isCompromised) - .flatMap((compromised) -> Mono.error(new CompromisedPasswordException( - "The provided password is compromised, please change your password"))); - } - private Mono upgradeEncodingIfNecessary(UserDetails userDetails, String presentedPassword) { boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(userDetails.getPassword()); @@ -193,16 +176,6 @@ public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } - /** - * Sets the {@link ReactiveCompromisedPasswordChecker} to be used before creating a - * successful authentication. Defaults to {@code null}. - * @param compromisedPasswordChecker the {@link CompromisedPasswordChecker} to use - * @since 6.3 - */ - public void setCompromisedPasswordChecker(ReactiveCompromisedPasswordChecker compromisedPasswordChecker) { - this.compromisedPasswordChecker = compromisedPasswordChecker; - } - /** * Allows subclasses to retrieve the UserDetails from an * implementation-specific location. diff --git a/core/src/main/java/org/springframework/security/authentication/AuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/AuthenticationProvider.java index 1659bcf3751..86e4c6e27ed 100644 --- a/core/src/main/java/org/springframework/security/authentication/AuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/authentication/AuthenticationProvider.java @@ -47,10 +47,10 @@ public interface AuthenticationProvider { *

* Returning true does not guarantee an * AuthenticationProvider will be able to authenticate the presented - * Authentication object. It simply indicates it can support closer - * evaluation of it. An AuthenticationProvider can still return - * null from the {@link #authenticate(Authentication)} method to indicate - * another AuthenticationProvider should be tried. + * instance of the Authentication class. It simply indicates it can + * support closer evaluation of it. An AuthenticationProvider can still + * return null from the {@link #authenticate(Authentication)} method to + * indicate another AuthenticationProvider should be tried. *

*

* Selection of an AuthenticationProvider capable of performing diff --git a/core/src/main/java/org/springframework/security/authentication/CachingUserDetailsService.java b/core/src/main/java/org/springframework/security/authentication/CachingUserDetailsService.java index ba8f6d3695a..39dc1f64877 100644 --- a/core/src/main/java/org/springframework/security/authentication/CachingUserDetailsService.java +++ b/core/src/main/java/org/springframework/security/authentication/CachingUserDetailsService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,33 +23,6 @@ import org.springframework.util.Assert; /** - * Implementation of {@link UserDetailsService} that utilizes caching through a - * {@link UserCache} - *

- * If a null {@link UserDetails} instance is returned from - * {@link UserCache#getUserFromCache(String)} to the {@link UserCache} got from - * {@link #getUserCache()}, the user load is deferred to the {@link UserDetailsService} - * provided during construction. Otherwise, the instance retrieved from the cache is - * returned. - *

- * It is initialized with a {@link NullUserCache} by default, so it's strongly recommended - * setting your own {@link UserCache} using {@link #setUserCache(UserCache)}, otherwise, - * the delegate will be called every time. - *

- * Utilize this class by defining a {@link org.springframework.context.annotation.Bean} - * that encapsulates an actual implementation of {@link UserDetailsService} and providing - * a {@link UserCache} implementation. - *

- * For example:
- * @Bean
- * public CachingUserDetailsService cachingUserDetailsService(UserCache userCache) {
- *     UserDetailsService delegate = ...;
- *     CachingUserDetailsService service = new CachingUserDetailsService(delegate);
- *     service.setUserCache(userCache);
- *     return service;
- * }
- * 
- * * @author Luke Taylor * @since 2.0 */ diff --git a/core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java b/core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java index 2fdc2d48c42..7a06a0695b5 100644 --- a/core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java +++ b/core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,7 @@ import java.util.Arrays; import java.util.List; -import java.util.function.Function; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -30,9 +27,8 @@ /** * A {@link ReactiveAuthenticationManager} that delegates to other - * {@link ReactiveAuthenticationManager} instances. When {@code continueOnError} is - * {@code true}, will continue until the first non-empty, non-error result; otherwise, - * will continue only until the first non-empty result. + * {@link ReactiveAuthenticationManager} instances using the result from the first non + * empty result. * * @author Rob Winch * @since 5.1 @@ -41,10 +37,6 @@ public class DelegatingReactiveAuthenticationManager implements ReactiveAuthenti private final List delegates; - private boolean continueOnError = false; - - private final Log logger = LogFactory.getLog(getClass()); - public DelegatingReactiveAuthenticationManager(ReactiveAuthenticationManager... entryPoints) { this(Arrays.asList(entryPoints)); } @@ -56,20 +48,11 @@ public DelegatingReactiveAuthenticationManager(List authenticate(Authentication authentication) { - Flux result = Flux.fromIterable(this.delegates); - Function> logging = (m) -> m.authenticate(authentication) - .doOnError(this.logger::debug); - - return ((this.continueOnError) ? result.concatMapDelayError(logging) : result.concatMap(logging)).next(); - } - - /** - * Continue iterating when a delegate errors, defaults to {@code false} - * @param continueOnError whether to continue when a delegate errors - * @since 6.3 - */ - public void setContinueOnError(boolean continueOnError) { - this.continueOnError = continueOnError; + // @formatter:off + return Flux.fromIterable(this.delegates) + .concatMap((m) -> m.authenticate(authentication)) + .next(); + // @formatter:on } } diff --git a/core/src/main/java/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java index a854d6d58d1..24fe918164b 100644 --- a/core/src/main/java/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java @@ -20,8 +20,6 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authentication.password.CompromisedPasswordChecker; -import org.springframework.security.authentication.password.CompromisedPasswordException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; @@ -62,8 +60,6 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication private UserDetailsPasswordService userDetailsPasswordService; - private CompromisedPasswordChecker compromisedPasswordChecker; - public DaoAuthenticationProvider() { this(PasswordEncoderFactories.createDelegatingPasswordEncoder()); } @@ -126,15 +122,10 @@ protected final UserDetails retrieveUser(String username, UsernamePasswordAuthen @Override protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { - String presentedPassword = authentication.getCredentials().toString(); - boolean isPasswordCompromised = this.compromisedPasswordChecker != null - && this.compromisedPasswordChecker.check(presentedPassword).isCompromised(); - if (isPasswordCompromised) { - throw new CompromisedPasswordException("The provided password is compromised, please change your password"); - } boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword()); if (upgradeEncoding) { + String presentedPassword = authentication.getCredentials().toString(); String newPassword = this.passwordEncoder.encode(presentedPassword); user = this.userDetailsPasswordService.updatePassword(user, newPassword); } @@ -183,14 +174,4 @@ public void setUserDetailsPasswordService(UserDetailsPasswordService userDetails this.userDetailsPasswordService = userDetailsPasswordService; } - /** - * Sets the {@link CompromisedPasswordChecker} to be used before creating a successful - * authentication. Defaults to {@code null}. - * @param compromisedPasswordChecker the {@link CompromisedPasswordChecker} to use - * @since 6.3 - */ - public void setCompromisedPasswordChecker(CompromisedPasswordChecker compromisedPasswordChecker) { - this.compromisedPasswordChecker = compromisedPasswordChecker; - } - } diff --git a/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordCheckResult.java b/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordCheckResult.java deleted file mode 100644 index ad52d9420ee..00000000000 --- a/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordCheckResult.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authentication.password; - -public class CompromisedPasswordCheckResult { - - private final boolean compromised; - - public CompromisedPasswordCheckResult(boolean compromised) { - this.compromised = compromised; - } - - public boolean isCompromised() { - return this.compromised; - } - -} diff --git a/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordChecker.java b/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordChecker.java deleted file mode 100644 index d76f35f5461..00000000000 --- a/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordChecker.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authentication.password; - -import org.springframework.lang.NonNull; - -/** - * An API for checking if a password has been compromised. - * - * @author Marcus da Coregio - * @since 6.3 - */ -public interface CompromisedPasswordChecker { - - /** - * Check whether the password is compromised - * @param password the password to check - * @return a non-null {@link CompromisedPasswordCheckResult} - */ - @NonNull - CompromisedPasswordCheckResult check(String password); - -} diff --git a/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordException.java b/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordException.java deleted file mode 100644 index 672876164fb..00000000000 --- a/core/src/main/java/org/springframework/security/authentication/password/CompromisedPasswordException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authentication.password; - -import org.springframework.security.core.AuthenticationException; - -/** - * Indicates that the provided password is compromised - * - * @author Marcus da Coregio - * @since 6.3 - */ -public class CompromisedPasswordException extends AuthenticationException { - - public CompromisedPasswordException(String message) { - super(message); - } - - public CompromisedPasswordException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/core/src/main/java/org/springframework/security/authentication/password/ReactiveCompromisedPasswordChecker.java b/core/src/main/java/org/springframework/security/authentication/password/ReactiveCompromisedPasswordChecker.java deleted file mode 100644 index 7d3ca6d6256..00000000000 --- a/core/src/main/java/org/springframework/security/authentication/password/ReactiveCompromisedPasswordChecker.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authentication.password; - -import reactor.core.publisher.Mono; - -/** - * A Reactive API for checking if a password has been compromised. - * - * @author Marcus da Coregio - * @since 6.3 - */ -public interface ReactiveCompromisedPasswordChecker { - - /** - * Check whether the password is compromised - * @param password the password to check - * @return a {@link Mono} containing the {@link CompromisedPasswordCheckResult} - */ - Mono check(String password); - -} diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationDecision.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationDecision.java index bd873ecdb17..7c9bdb79b2e 100644 --- a/core/src/main/java/org/springframework/security/authorization/AuthorizationDecision.java +++ b/core/src/main/java/org/springframework/security/authorization/AuthorizationDecision.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -20,7 +20,7 @@ * @author Rob Winch * @since 5.0 */ -public class AuthorizationDecision implements AuthorizationResult { +public class AuthorizationDecision { private final boolean granted; @@ -28,7 +28,6 @@ public AuthorizationDecision(boolean granted) { this.granted = granted; } - @Override public boolean isGranted() { return this.granted; } diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationDeniedException.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationDeniedException.java deleted file mode 100644 index 4bd8fd2bf66..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/AuthorizationDeniedException.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.util.Assert; - -/** - * An {@link AccessDeniedException} that contains the {@link AuthorizationResult} - * - * @author Marcus da Coregio - * @since 6.3 - */ -public class AuthorizationDeniedException extends AccessDeniedException implements AuthorizationResult { - - private final AuthorizationResult result; - - public AuthorizationDeniedException(String msg, AuthorizationResult authorizationResult) { - super(msg); - Assert.notNull(authorizationResult, "authorizationResult cannot be null"); - Assert.isTrue(!authorizationResult.isGranted(), "Granted authorization results are not supported"); - this.result = authorizationResult; - } - - public AuthorizationResult getAuthorizationResult() { - return this.result; - } - - @Override - public boolean isGranted() { - return false; - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationManagers.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationManagers.java index b9031092050..6fa0ba80491 100644 --- a/core/src/main/java/org/springframework/security/authorization/AuthorizationManagers.java +++ b/core/src/main/java/org/springframework/security/authorization/AuthorizationManagers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -23,7 +23,6 @@ * A factory class to create an {@link AuthorizationManager} instances. * * @author Evgeniy Cheban - * @author Josh Cummings * @since 5.8 */ public final class AuthorizationManagers { @@ -38,23 +37,6 @@ public final class AuthorizationManagers { */ @SafeVarargs public static AuthorizationManager anyOf(AuthorizationManager... managers) { - return anyOf(new AuthorizationDecision(false), managers); - } - - /** - * Creates an {@link AuthorizationManager} that grants access if at least one - * {@link AuthorizationManager} granted, if managers are empty or - * abstained, a default {@link AuthorizationDecision} is returned. - * @param the type of object that is being authorized - * @param allAbstainDefaultDecision the default decision if all - * {@link AuthorizationManager}s abstained - * @param managers the {@link AuthorizationManager}s to use - * @return the {@link AuthorizationManager} to use - * @since 6.3 - */ - @SafeVarargs - public static AuthorizationManager anyOf(AuthorizationDecision allAbstainDefaultDecision, - AuthorizationManager... managers) { return (authentication, object) -> { List decisions = new ArrayList<>(); for (AuthorizationManager manager : managers) { @@ -68,7 +50,7 @@ public static AuthorizationManager anyOf(AuthorizationDecision allAbstain decisions.add(decision); } if (decisions.isEmpty()) { - return allAbstainDefaultDecision; + return new AuthorizationDecision(false); } return new CompositeAuthorizationDecision(false, decisions); }; @@ -84,23 +66,6 @@ public static AuthorizationManager anyOf(AuthorizationDecision allAbstain */ @SafeVarargs public static AuthorizationManager allOf(AuthorizationManager... managers) { - return allOf(new AuthorizationDecision(true), managers); - } - - /** - * Creates an {@link AuthorizationManager} that grants access if all - * {@link AuthorizationManager}s granted, if managers are empty or - * abstained, a default {@link AuthorizationDecision} is returned. - * @param the type of object that is being authorized - * @param allAbstainDefaultDecision the default decision if all - * {@link AuthorizationManager}s abstained - * @param managers the {@link AuthorizationManager}s to use - * @return the {@link AuthorizationManager} to use - * @since 6.3 - */ - @SafeVarargs - public static AuthorizationManager allOf(AuthorizationDecision allAbstainDefaultDecision, - AuthorizationManager... managers) { return (authentication, object) -> { List decisions = new ArrayList<>(); for (AuthorizationManager manager : managers) { @@ -114,31 +79,12 @@ public static AuthorizationManager allOf(AuthorizationDecision allAbstain decisions.add(decision); } if (decisions.isEmpty()) { - return allAbstainDefaultDecision; + return new AuthorizationDecision(true); } return new CompositeAuthorizationDecision(true, decisions); }; } - /** - * Creates an {@link AuthorizationManager} that reverses whatever decision the given - * {@link AuthorizationManager} granted. If the given {@link AuthorizationManager} - * abstains, then the returned manager also abstains. - * @param the type of object that is being authorized - * @param manager the {@link AuthorizationManager} to reverse - * @return the reversing {@link AuthorizationManager} - * @since 6.3 - */ - public static AuthorizationManager not(AuthorizationManager manager) { - return (authentication, object) -> { - AuthorizationDecision decision = manager.check(authentication, object); - if (decision == null) { - return null; - } - return new NotAuthorizationDecision(decision); - }; - } - private AuthorizationManagers() { } @@ -158,20 +104,4 @@ public String toString() { } - private static final class NotAuthorizationDecision extends AuthorizationDecision { - - private final AuthorizationDecision decision; - - private NotAuthorizationDecision(AuthorizationDecision decision) { - super(!decision.isGranted()); - this.decision = decision; - } - - @Override - public String toString() { - return "NotAuthorizationDecision [decision=" + this.decision + ']'; - } - - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationProxyFactory.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationProxyFactory.java deleted file mode 100644 index c425db9a01d..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/AuthorizationProxyFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization; - -/** - * A factory for wrapping arbitrary objects in authorization-related advice - * - * @author Josh Cummings - * @since 6.3 - * @see org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory - */ -public interface AuthorizationProxyFactory { - - /** - * Wrap the given {@code object} in authorization-related advice. - * - *

- * Please check the implementation for which kinds of objects it supports. - * @param object the object to proxy - * @return the proxied object - * @throws org.springframework.aop.framework.AopConfigException if a proxy cannot be - * created - */ - Object proxy(Object object); - -} diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationResult.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationResult.java deleted file mode 100644 index 11c5cd4a769..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/AuthorizationResult.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization; - -/** - * Represents an authorization result - * - * @author Marcus da Coregio - * @since 6.3 - */ -public interface AuthorizationResult { - - /** - * @return whether the access has been granted - */ - boolean isGranted(); - -} diff --git a/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java index 00deb1c0353..943e46dce3b 100644 --- a/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java @@ -21,15 +21,11 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationConvention; import io.micrometer.observation.ObservationRegistry; -import org.aopalliance.intercept.MethodInvocation; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodInvocationResult; -import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; import org.springframework.security.core.Authentication; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.util.Assert; @@ -40,8 +36,7 @@ * @author Josh Cummings * @since 6.0 */ -public final class ObservationAuthorizationManager - implements AuthorizationManager, MessageSourceAware, MethodAuthorizationDeniedHandler { +public final class ObservationAuthorizationManager implements AuthorizationManager, MessageSourceAware { private final ObservationRegistry registry; @@ -51,14 +46,9 @@ public final class ObservationAuthorizationManager private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager delegate) { this.registry = registry; this.delegate = delegate; - if (delegate instanceof MethodAuthorizationDeniedHandler h) { - this.handler = h; - } } @Override @@ -108,15 +98,4 @@ public void setMessageSource(final MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult); - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, - AuthorizationResult authorizationResult) { - return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java index d6e7a2b2c63..83f4cb0609f 100644 --- a/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java @@ -20,13 +20,9 @@ import io.micrometer.observation.ObservationConvention; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; -import org.aopalliance.intercept.MethodInvocation; import reactor.core.publisher.Mono; import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodInvocationResult; -import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -36,8 +32,7 @@ * @author Josh Cummings * @since 6.0 */ -public final class ObservationReactiveAuthorizationManager - implements ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { +public final class ObservationReactiveAuthorizationManager implements ReactiveAuthorizationManager { private final ObservationRegistry registry; @@ -45,15 +40,10 @@ public final class ObservationReactiveAuthorizationManager private ObservationConvention> convention = new AuthorizationObservationConvention(); - private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - public ObservationReactiveAuthorizationManager(ObservationRegistry registry, ReactiveAuthorizationManager delegate) { this.registry = registry; this.delegate = delegate; - if (delegate instanceof MethodAuthorizationDeniedHandler h) { - this.handler = h; - } } @Override @@ -91,15 +81,4 @@ public void setObservationConvention(ObservationConvention { private final Map cachedAttributes = new ConcurrentHashMap<>(); - private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); - - private PrePostTemplateDefaults defaults; - /** * Returns an {@link ExpressionAttribute} for the {@link MethodInvocation}. * @param mi the {@link MethodInvocation} to use @@ -68,28 +57,6 @@ final T getAttribute(Method method, Class targetClass) { return this.cachedAttributes.computeIfAbsent(cacheKey, (k) -> resolveAttribute(method, targetClass)); } - final Function findUniqueAnnotation(Class type) { - return (this.defaults != null) ? AuthorizationAnnotationUtils.withDefaults(type, this.defaults) - : AuthorizationAnnotationUtils.withDefaults(type); - } - - /** - * Returns the {@link MethodSecurityExpressionHandler}. - * @return the {@link MethodSecurityExpressionHandler} to use - */ - MethodSecurityExpressionHandler getExpressionHandler() { - return this.expressionHandler; - } - - void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.expressionHandler = expressionHandler; - } - - void setTemplateDefaults(PrePostTemplateDefaults defaults) { - this.defaults = defaults; - } - /** * Subclasses should implement this method to provide the non-null * {@link ExpressionAttribute} for the method and the target class. @@ -100,8 +67,4 @@ void setTemplateDefaults(PrePostTemplateDefaults defaults) { @NonNull abstract T resolveAttribute(Method method, Class targetClass); - Class targetClass(Method method, Class targetClass) { - return (targetClass != null) ? targetClass : method.getDeclaringClass(); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisor.java deleted file mode 100644 index deca6b04406..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisor.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import org.aopalliance.intercept.MethodInterceptor; - -import org.springframework.aop.PointcutAdvisor; -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.core.Ordered; - -/** - * An interface that indicates method security advice - * - * @author Josh Cummings - * @since 6.3 - * @see AuthorizationManagerBeforeMethodInterceptor - * @see AuthorizationManagerAfterMethodInterceptor - * @see PreFilterAuthorizationMethodInterceptor - * @see PostFilterAuthorizationMethodInterceptor - */ -public interface AuthorizationAdvisor extends Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisorProxyFactory.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisorProxyFactory.java deleted file mode 100644 index 42248b3da26..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisorProxyFactory.java +++ /dev/null @@ -1,542 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import java.lang.reflect.Array; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Queue; -import java.util.Set; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.aop.Advisor; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.lang.NonNull; -import org.springframework.security.authorization.AuthorizationProxyFactory; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -/** - * A proxy factory for applying authorization advice to an arbitrary object. - * - *

- * For example, consider a non-Spring-managed object {@code Foo}:

- *     class Foo {
- *         @PreAuthorize("hasAuthority('bar:read')")
- *         String bar() { ... }
- *     }
- * 
- * - * Use {@link AuthorizationAdvisorProxyFactory} to wrap the instance in Spring Security's - * {@link org.springframework.security.access.prepost.PreAuthorize} method interceptor - * like so: - * - *
- *     AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
- *     Foo foo = new Foo();
- *     foo.bar(); // passes
- *     Foo securedFoo = proxyFactory.proxy(foo);
- *     securedFoo.bar(); // access denied!
- * 
- * - * @author Josh Cummings - * @since 6.3 - */ -public final class AuthorizationAdvisorProxyFactory - implements AuthorizationProxyFactory, Iterable { - - private static final boolean isReactivePresent = ClassUtils.isPresent("reactor.core.publisher.Mono", null); - - private static final TargetVisitor DEFAULT_VISITOR = isReactivePresent - ? TargetVisitor.of(new ClassVisitor(), new ReactiveTypeVisitor(), new ContainerTypeVisitor()) - : TargetVisitor.of(new ClassVisitor(), new ContainerTypeVisitor()); - - private static final TargetVisitor DEFAULT_VISITOR_SKIP_VALUE_TYPES = TargetVisitor.of(new ClassVisitor(), - new IgnoreValueTypeVisitor(), DEFAULT_VISITOR); - - private List advisors; - - private TargetVisitor visitor = DEFAULT_VISITOR; - - private AuthorizationAdvisorProxyFactory(List advisors) { - this.advisors = new ArrayList<>(advisors); - this.advisors.add(new AuthorizeReturnObjectMethodInterceptor(this)); - setAdvisors(this.advisors); - } - - /** - * Construct an {@link AuthorizationAdvisorProxyFactory} with the defaults needed for - * wrapping objects in Spring Security's pre-post method security support. - * @return an {@link AuthorizationAdvisorProxyFactory} for adding pre-post method - * security support - */ - public static AuthorizationAdvisorProxyFactory withDefaults() { - List advisors = new ArrayList<>(); - advisors.add(AuthorizationManagerBeforeMethodInterceptor.preAuthorize()); - advisors.add(AuthorizationManagerAfterMethodInterceptor.postAuthorize()); - advisors.add(new PreFilterAuthorizationMethodInterceptor()); - advisors.add(new PostFilterAuthorizationMethodInterceptor()); - return new AuthorizationAdvisorProxyFactory(advisors); - } - - /** - * Construct an {@link AuthorizationAdvisorProxyFactory} with the defaults needed for - * wrapping objects in Spring Security's pre-post reactive method security support. - * @return an {@link AuthorizationAdvisorProxyFactory} for adding pre-post reactive - * method security support - */ - public static AuthorizationAdvisorProxyFactory withReactiveDefaults() { - List advisors = new ArrayList<>(); - advisors.add(AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize()); - advisors.add(AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize()); - advisors.add(new PreFilterAuthorizationReactiveMethodInterceptor()); - advisors.add(new PostFilterAuthorizationReactiveMethodInterceptor()); - return new AuthorizationAdvisorProxyFactory(advisors); - } - - /** - * Proxy an object to enforce authorization advice. - * - *

- * Proxies any instance of a non-final class or a class that implements more than one - * interface. - * - *

- * If {@code target} is an {@link Iterator}, {@link Collection}, {@link Array}, - * {@link Map}, {@link Stream}, or {@link Optional}, then the element or value type is - * proxied. - * - *

- * If {@code target} is a {@link Class}, then {@link ProxyFactory#getProxyClass} is - * invoked instead. - * @param target the instance to proxy - * @return the proxied instance - */ - @Override - public Object proxy(Object target) { - if (target == null) { - return null; - } - Object proxied = this.visitor.visit(this, target); - if (proxied != null) { - return proxied; - } - ProxyFactory factory = new ProxyFactory(target); - for (Advisor advisor : this.advisors) { - factory.addAdvisors(advisor); - } - factory.setProxyTargetClass(!Modifier.isFinal(target.getClass().getModifiers())); - return factory.getProxy(); - } - - /** - * Add advisors that should be included to each proxy created. - * - *

- * All advisors are re-sorted by their advisor order. - * @param advisors the advisors to add - */ - public void setAdvisors(AuthorizationAdvisor... advisors) { - this.advisors = new ArrayList<>(List.of(advisors)); - AnnotationAwareOrderComparator.sort(this.advisors); - } - - /** - * Add advisors that should be included to each proxy created. - * - *

- * All advisors are re-sorted by their advisor order. - * @param advisors the advisors to add - */ - public void setAdvisors(Collection advisors) { - this.advisors = new ArrayList<>(advisors); - AnnotationAwareOrderComparator.sort(this.advisors); - } - - /** - * Use this visitor to navigate the proxy target's hierarchy. - * - *

- * This can be helpful when you want a specialized behavior for a type or set of - * types. For example, if you want to have this factory skip primitives and wrappers, - * then you can do: - * - *

-	 * 	AuthorizationAdvisorProxyFactory proxyFactory = new AuthorizationAdvisorProxyFactory();
-	 * 	proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
-	 * 
- * - *

- * The default {@link TargetVisitor} proxies {@link Class} instances as well as - * instances contained in reactive types (if reactor is present), collection types, - * and other container types like {@link Optional} and {@link Supplier}. - * - *

- * If you want to add support for another container type, you can do so in the - * following way: - * - *

-	 * 	TargetVisitor functions = (factory, target) -> {
-	 *		if (target instanceof Function function) {
-	 *			return (input) -> factory.proxy(function.apply(input));
-	 *		}
-	 *		return null;
-	 * 	};
-	 * 	AuthorizationAdvisorProxyFactory proxyFactory = new AuthorizationAdvisorProxyFactory();
-	 * 	proxyFactory.setTargetVisitor(TargetVisitor.of(functions, TargetVisitor.defaultsSkipValueTypes()));
-	 * 
- * @param visitor the visitor to use to introduce specialized behavior for a type - * @see TargetVisitor#defaults - */ - public void setTargetVisitor(TargetVisitor visitor) { - Assert.notNull(visitor, "delegate cannot be null"); - this.visitor = visitor; - } - - @Override - @NonNull - public Iterator iterator() { - return this.advisors.iterator(); - } - - /** - * An interface to handle how the {@link AuthorizationAdvisorProxyFactory} should step - * through the target's object hierarchy. - * - * @author Josh Cummings - * @since 6.3 - * @see AuthorizationAdvisorProxyFactory#setTargetVisitor - */ - public interface TargetVisitor { - - /** - * Visit and possibly proxy this object. - * - *

- * Visiting may take the form of walking down this object's hierarchy and proxying - * sub-objects. - * - *

- * An example is a visitor that proxies the elements of a {@link List} instead of - * the list itself - * - *

- * Returning {@code null} implies that this visitor does not want to proxy this - * object - * @param proxyFactory the proxy factory to delegate proxying to for any - * sub-objects - * @param target the object to proxy - * @return the visited (and possibly proxied) object - */ - Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target); - - /** - * The default {@link TargetVisitor}, which will proxy {@link Class} instances as - * well as instances contained in reactive types (if reactor is present), - * collection types, and other container types like {@link Optional} and - * {@link Supplier} - */ - static TargetVisitor defaults() { - return AuthorizationAdvisorProxyFactory.DEFAULT_VISITOR; - } - - /** - * The default {@link TargetVisitor} that also skips any value types (for example, - * {@link String}, {@link Integer}). This is handy for annotations like - * {@link AuthorizeReturnObject} when used at the class level - */ - static TargetVisitor defaultsSkipValueTypes() { - return AuthorizationAdvisorProxyFactory.DEFAULT_VISITOR_SKIP_VALUE_TYPES; - } - - /** - * Compose a set of visitors. This is helpful when you are customizing for a given - * type and still want the defaults applied for the remaining types. - * - *

- * The resulting visitor will execute the first visitor that returns a non-null - * value. - * @param visitors the set of visitors - * @return a composite that executes the first visitor that returns a non-null - * value - */ - static TargetVisitor of(TargetVisitor... visitors) { - return (proxyFactory, target) -> { - for (TargetVisitor visitor : visitors) { - Object result = visitor.visit(proxyFactory, target); - if (result != null) { - return result; - } - } - return null; - }; - } - - } - - private static final class IgnoreValueTypeVisitor implements TargetVisitor { - - @Override - public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object object) { - if (ClassUtils.isSimpleValueType(object.getClass())) { - return object; - } - return null; - } - - } - - private static final class ClassVisitor implements TargetVisitor { - - @Override - public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object object) { - if (object instanceof Class targetClass) { - ProxyFactory factory = new ProxyFactory(); - factory.setTargetClass(targetClass); - factory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass)); - factory.setProxyTargetClass(!Modifier.isFinal(targetClass.getModifiers())); - for (Advisor advisor : proxyFactory) { - factory.addAdvisors(advisor); - } - return factory.getProxyClass(getClass().getClassLoader()); - } - return null; - } - - } - - private static final class ContainerTypeVisitor implements TargetVisitor { - - @Override - public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) { - if (target instanceof Iterator iterator) { - return proxyIterator(proxyFactory, iterator); - } - if (target instanceof Queue queue) { - return proxyQueue(proxyFactory, queue); - } - if (target instanceof List list) { - return proxyList(proxyFactory, list); - } - if (target instanceof SortedSet set) { - return proxySortedSet(proxyFactory, set); - } - if (target instanceof Set set) { - return proxySet(proxyFactory, set); - } - if (target.getClass().isArray()) { - return proxyArray(proxyFactory, (Object[]) target); - } - if (target instanceof SortedMap map) { - return proxySortedMap(proxyFactory, map); - } - if (target instanceof Iterable iterable) { - return proxyIterable(proxyFactory, iterable); - } - if (target instanceof Map map) { - return proxyMap(proxyFactory, map); - } - if (target instanceof Stream stream) { - return proxyStream(proxyFactory, stream); - } - if (target instanceof Optional optional) { - return proxyOptional(proxyFactory, optional); - } - if (target instanceof Supplier supplier) { - return proxySupplier(proxyFactory, supplier); - } - return null; - } - - @SuppressWarnings("unchecked") - private T proxyCast(AuthorizationProxyFactory proxyFactory, T target) { - return (T) proxyFactory.proxy(target); - } - - private Iterable proxyIterable(AuthorizationProxyFactory proxyFactory, Iterable iterable) { - return () -> proxyIterator(proxyFactory, iterable.iterator()); - } - - private Iterator proxyIterator(AuthorizationProxyFactory proxyFactory, Iterator iterator) { - return new Iterator<>() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public T next() { - return proxyCast(proxyFactory, iterator.next()); - } - }; - } - - private SortedSet proxySortedSet(AuthorizationProxyFactory proxyFactory, SortedSet set) { - SortedSet proxies = new TreeSet<>(set.comparator()); - for (T toProxy : set) { - proxies.add(proxyCast(proxyFactory, toProxy)); - } - try { - set.clear(); - set.addAll(proxies); - return proxies; - } - catch (UnsupportedOperationException ex) { - return Collections.unmodifiableSortedSet(proxies); - } - } - - private Set proxySet(AuthorizationProxyFactory proxyFactory, Set set) { - Set proxies = new LinkedHashSet<>(set.size()); - for (T toProxy : set) { - proxies.add(proxyCast(proxyFactory, toProxy)); - } - try { - set.clear(); - set.addAll(proxies); - return proxies; - } - catch (UnsupportedOperationException ex) { - return Collections.unmodifiableSet(proxies); - } - } - - private Queue proxyQueue(AuthorizationProxyFactory proxyFactory, Queue queue) { - Queue proxies = new LinkedList<>(); - for (T toProxy : queue) { - proxies.add(proxyCast(proxyFactory, toProxy)); - } - queue.clear(); - queue.addAll(proxies); - return proxies; - } - - private List proxyList(AuthorizationProxyFactory proxyFactory, List list) { - List proxies = new ArrayList<>(list.size()); - for (T toProxy : list) { - proxies.add(proxyCast(proxyFactory, toProxy)); - } - try { - list.clear(); - list.addAll(proxies); - return proxies; - } - catch (UnsupportedOperationException ex) { - return Collections.unmodifiableList(proxies); - } - } - - private Object[] proxyArray(AuthorizationProxyFactory proxyFactory, Object[] objects) { - List retain = new ArrayList<>(objects.length); - for (Object object : objects) { - retain.add(proxyFactory.proxy(object)); - } - Object[] proxies = (Object[]) Array.newInstance(objects.getClass().getComponentType(), retain.size()); - for (int i = 0; i < retain.size(); i++) { - proxies[i] = retain.get(i); - } - return proxies; - } - - private SortedMap proxySortedMap(AuthorizationProxyFactory proxyFactory, SortedMap entries) { - SortedMap proxies = new TreeMap<>(entries.comparator()); - for (Map.Entry entry : entries.entrySet()) { - proxies.put(entry.getKey(), proxyCast(proxyFactory, entry.getValue())); - } - try { - entries.clear(); - entries.putAll(proxies); - return entries; - } - catch (UnsupportedOperationException ex) { - return Collections.unmodifiableSortedMap(proxies); - } - } - - private Map proxyMap(AuthorizationProxyFactory proxyFactory, Map entries) { - Map proxies = new LinkedHashMap<>(entries.size()); - for (Map.Entry entry : entries.entrySet()) { - proxies.put(entry.getKey(), proxyCast(proxyFactory, entry.getValue())); - } - try { - entries.clear(); - entries.putAll(proxies); - return entries; - } - catch (UnsupportedOperationException ex) { - return Collections.unmodifiableMap(proxies); - } - } - - private Stream proxyStream(AuthorizationProxyFactory proxyFactory, Stream stream) { - return stream.map(proxyFactory::proxy).onClose(stream::close); - } - - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private Optional proxyOptional(AuthorizationProxyFactory proxyFactory, Optional optional) { - return optional.map(proxyFactory::proxy); - } - - private Supplier proxySupplier(AuthorizationProxyFactory proxyFactory, Supplier supplier) { - return () -> proxyFactory.proxy(supplier.get()); - } - - } - - private static class ReactiveTypeVisitor implements TargetVisitor { - - @Override - @SuppressWarnings("ReactiveStreamsUnusedPublisher") - public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) { - if (target instanceof Mono mono) { - return proxyMono(proxyFactory, mono); - } - if (target instanceof Flux flux) { - return proxyFlux(proxyFactory, flux); - } - return null; - } - - private Mono proxyMono(AuthorizationProxyFactory proxyFactory, Mono mono) { - return mono.map(proxyFactory::proxy); - } - - private Flux proxyFlux(AuthorizationProxyFactory proxyFactory, Flux flux) { - return flux.map(proxyFactory::proxy); - } - - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java index de0e9c9111e..fa883a2089c 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -17,100 +17,56 @@ package org.springframework.security.authorization.method; import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Executable; import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; import org.springframework.core.annotation.AnnotationConfigurationException; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.core.annotation.RepeatableContainers; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.util.PropertyPlaceholderHelper; /** - * A collection of utility methods that check for, and error on, conflicting annotations. - * This is specifically important for Spring Security annotations which are not designed - * to be repeatable. + * A wrapper around {@link AnnotationUtils} that checks for, and errors on, conflicting + * annotations. This is specifically important for Spring Security annotations which are + * not designed to be repeatable. * - *

* There are numerous ways that two annotations of the same type may be attached to the * same method. For example, a class may implement a method defined in two separate - * interfaces. If both of those interfaces have a {@code @PreAuthorize} annotation, then - * it's unclear which {@code @PreAuthorize} expression Spring Security should use. + * interfaces. If both of those interfaces have a `@PreAuthorize` annotation, then it's + * unclear which `@PreAuthorize` expression Spring Security should use. * - *

* Another way is when one of Spring Security's annotations is used as a meta-annotation. * In that case, two custom annotations can be declared, each with their own - * {@code @PreAuthorize} declaration. If both custom annotations are used on the same - * method, then it's unclear which {@code @PreAuthorize} expression Spring Security should - * use. + * `@PreAuthorize` declaration. If both custom annotations are used on the same method, + * then it's unclear which `@PreAuthorize` expression Spring Security should use. * * @author Josh Cummings - * @author Sam Brannen */ final class AuthorizationAnnotationUtils { - static Function withDefaults(Class type, - PrePostTemplateDefaults defaults) { - Function, A> map = (mergedAnnotation) -> { - if (mergedAnnotation.getMetaSource() == null) { - return mergedAnnotation.synthesize(); - } - PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("{", "}", null, - defaults.isIgnoreUnknown()); - String expression = (String) mergedAnnotation.asMap().get("value"); - Map annotationProperties = mergedAnnotation.getMetaSource().asMap(); - Map stringProperties = new HashMap<>(); - for (Map.Entry property : annotationProperties.entrySet()) { - String key = property.getKey(); - Object value = property.getValue(); - String asString = (value instanceof String) ? (String) value - : DefaultConversionService.getSharedInstance().convert(value, String.class); - stringProperties.put(key, asString); - } - AnnotatedElement annotatedElement = (AnnotatedElement) mergedAnnotation.getSource(); - String value = helper.replacePlaceholders(expression, stringProperties::get); - Map properties = new HashMap<>(mergedAnnotation.asMap()); - properties.put("value", value); - return MergedAnnotation.of(annotatedElement, type, properties).synthesize(); - }; - return (annotatedElement) -> findDistinctAnnotation(annotatedElement, type, map); - } - - static Function withDefaults(Class type) { - return (annotatedElement) -> findDistinctAnnotation(annotatedElement, type, MergedAnnotation::synthesize); - } - - static A findUniqueAnnotation(Method method, Class annotationType) { - return findDistinctAnnotation(method, annotationType, MergedAnnotation::synthesize); - } - - static A findUniqueAnnotation(Class type, Class annotationType) { - return findDistinctAnnotation(type, annotationType, MergedAnnotation::synthesize); - } - /** * Perform an exhaustive search on the type hierarchy of the given {@link Method} for * the annotation of type {@code annotationType}, including any annotations using * {@code annotationType} as a meta-annotation. * - *

- * If more than one unique annotation is found, then throw an error. + * If more than one is found, then throw an error. * @param method the method declaration to search from * @param annotationType the annotation type to search for - * @return a unique instance of the annotation attributed to the method, {@code null} - * otherwise - * @throws AnnotationConfigurationException if more than one unique instance of the + * @return the unique instance of the annotation attributed to the method, + * {@code null} otherwise + * @throws AnnotationConfigurationException if more than one instance of the * annotation is found */ - static A findUniqueAnnotation(Method method, Class annotationType, - Function, A> map) { - return findDistinctAnnotation(method, annotationType, map); + static A findUniqueAnnotation(Method method, Class annotationType) { + MergedAnnotations mergedAnnotations = MergedAnnotations.from(method, + MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none()); + if (hasDuplicate(mergedAnnotations, annotationType)) { + throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType + + " attributed to " + method + + " Please remove the duplicate annotations and publish a bean to handle your authorization logic."); + } + return AnnotationUtils.findAnnotation(method, annotationType); } /** @@ -118,38 +74,60 @@ static A findUniqueAnnotation(Method method, Class ann * the annotation of type {@code annotationType}, including any annotations using * {@code annotationType} as a meta-annotation. * - *

- * If more than one unique annotation is found, then throw an error. + * If more than one is found, then throw an error. * @param type the type to search from * @param annotationType the annotation type to search for - * @return a unique instance of the annotation attributed to the class, {@code null} - * otherwise - * @throws AnnotationConfigurationException if more than one unique instance of the + * @return the unique instance of the annotation attributed to the method, + * {@code null} otherwise + * @throws AnnotationConfigurationException if more than one instance of the * annotation is found */ - static A findUniqueAnnotation(Class type, Class annotationType, - Function, A> map) { - return findDistinctAnnotation(type, annotationType, map); + static A findUniqueAnnotation(Class type, Class annotationType) { + MergedAnnotations mergedAnnotations = MergedAnnotations.from(type, + MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none()); + if (hasDuplicate(mergedAnnotations, annotationType)) { + throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType + + " attributed to " + type + + " Please remove the duplicate annotations and publish a bean to handle your authorization logic."); + } + return AnnotationUtils.findAnnotation(type, annotationType); } - private static A findDistinctAnnotation(AnnotatedElement annotatedElement, - Class annotationType, Function, A> map) { - MergedAnnotations mergedAnnotations = MergedAnnotations.from(annotatedElement, SearchStrategy.TYPE_HIERARCHY, - RepeatableContainers.none()); - List annotations = mergedAnnotations.stream(annotationType) - .map(MergedAnnotation::withNonMergedAttributes) - .map(map) - .distinct() - .toList(); - - return switch (annotations.size()) { - case 0 -> null; - case 1 -> annotations.get(0); - default -> throw new AnnotationConfigurationException(""" - Please ensure there is one unique annotation of type @%s attributed to %s. \ - Found %d competing annotations: %s""".formatted(annotationType.getName(), annotatedElement, - annotations.size(), annotations)); - }; + private static boolean hasDuplicate(MergedAnnotations mergedAnnotations, + Class annotationType) { + MergedAnnotation alreadyFound = null; + for (MergedAnnotation mergedAnnotation : mergedAnnotations) { + if (isSynthetic(mergedAnnotation.getSource())) { + continue; + } + + if (mergedAnnotation.getType() != annotationType) { + continue; + } + + if (alreadyFound == null) { + alreadyFound = mergedAnnotation; + continue; + } + + // https://github.com/spring-projects/spring-framework/issues/31803 + if (!mergedAnnotation.getSource().equals(alreadyFound.getSource())) { + return true; + } + + if (mergedAnnotation.getRoot().getType() != alreadyFound.getRoot().getType()) { + return true; + } + } + return false; + } + + private static boolean isSynthetic(Object object) { + if (object instanceof Executable) { + return ((Executable) object).isSynthetic(); + } + + return false; } private AuthorizationAnnotationUtils() { diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationInterceptorsOrder.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationInterceptorsOrder.java index c92d39db185..da6a26bf6e0 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationInterceptorsOrder.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationInterceptorsOrder.java @@ -43,14 +43,12 @@ public enum AuthorizationInterceptorsOrder { JSR250, - SECURE_RESULT(450), - - POST_AUTHORIZE(500), + POST_AUTHORIZE, /** * {@link PostFilterAuthorizationMethodInterceptor} */ - POST_FILTER(600), + POST_FILTER, LAST(Integer.MAX_VALUE); diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java index 63448a6472a..2971589e336 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -25,12 +25,14 @@ import org.apache.commons.logging.LogFactory; import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.core.Ordered; import org.springframework.core.log.LogMessage; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; @@ -46,7 +48,8 @@ * @author Josh Cummings * @since 5.6 */ -public final class AuthorizationManagerAfterMethodInterceptor implements AuthorizationAdvisor { +public final class AuthorizationManagerAfterMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { private Supplier securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy; @@ -56,8 +59,6 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori private final AuthorizationManager authorizationManager; - private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler(); - private int order; private AuthorizationEventPublisher eventPublisher = AuthorizationManagerAfterMethodInterceptor::noPublish; @@ -118,17 +119,9 @@ public static AuthorizationManagerAfterMethodInterceptor postAuthorize( */ @Override public Object invoke(MethodInvocation mi) throws Throwable { - Object result; - try { - result = mi.proceed(); - } - catch (AuthorizationDeniedException ex) { - if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { - return handler.handleDeniedInvocation(mi, ex); - } - return this.defaultHandler.handleDeniedInvocation(mi, ex); - } - return attemptAuthorization(mi, result); + Object result = mi.proceed(); + attemptAuthorization(mi, result); + return result; } @Override @@ -179,7 +172,7 @@ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strat this.securityContextHolderStrategy = () -> strategy; } - private Object attemptAuthorization(MethodInvocation mi, Object result) { + private void attemptAuthorization(MethodInvocation mi, Object result) { this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi)); MethodInvocationResult object = new MethodInvocationResult(mi, result); AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, object); @@ -187,17 +180,9 @@ private Object attemptAuthorization(MethodInvocation mi, Object result) { if (decision != null && !decision.isGranted()) { this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager " + this.authorizationManager + " and decision " + decision)); - return handlePostInvocationDenied(object, decision); + throw new AccessDeniedException("Access Denied"); } this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi)); - return result; - } - - private Object handlePostInvocationDenied(MethodInvocationResult mi, AuthorizationDecision decision) { - if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler deniedHandler) { - return deniedHandler.handleDeniedInvocationResult(mi, decision); - } - return this.defaultHandler.handleDeniedInvocationResult(mi, decision); } private Authentication getAuthentication() { diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java index fa53945a69d..550b8fbdef3 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -26,17 +26,16 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.publisher.Signal; import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; +import org.springframework.core.Ordered; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationDeniedException; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -49,7 +48,8 @@ * @author Evgeniy Cheban * @since 5.8 */ -public final class AuthorizationManagerAfterReactiveMethodInterceptor implements AuthorizationAdvisor { +public final class AuthorizationManagerAfterReactiveMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow"; @@ -61,8 +61,6 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements private int order = AuthorizationInterceptorsOrder.LAST.getOrder(); - private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler(); - /** * Creates an instance for the {@link PostAuthorize} annotation. * @return the {@link AuthorizationManagerAfterReactiveMethodInterceptor} to use @@ -119,39 +117,27 @@ public Object invoke(MethodInvocation mi) throws Throwable { + "(for example, a Mono or Flux) or the function must be a Kotlin coroutine " + "in order to support Reactor Context"); Mono authentication = ReactiveAuthenticationUtils.getAuthentication(); - Function, Mono> postAuthorize = (signal) -> { - if (signal.isOnComplete()) { - return Mono.empty(); - } - if (!signal.hasError()) { - return postAuthorize(authentication, mi, signal.get()); - } - if (signal.getThrowable() instanceof AuthorizationDeniedException denied) { - return postProcess(denied, mi); - } - return Mono.error(signal.getThrowable()); - }; + Function> postAuthorize = (result) -> postAuthorize(authentication, mi, result); ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); if (hasFlowReturnType) { if (isSuspendingFunction) { Publisher publisher = ReactiveMethodInvocationUtils.proceed(mi); - return Flux.from(publisher).materialize().flatMap(postAuthorize); + return Flux.from(publisher).flatMap(postAuthorize); } else { Assert.state(adapter != null, () -> "The returnType " + type + " on " + method + " must have a org.springframework.core.ReactiveAdapter registered"); Flux response = Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi))) - .materialize() .flatMap(postAuthorize); return KotlinDelegate.asFlow(response); } } Publisher publisher = ReactiveMethodInvocationUtils.proceed(mi); if (isMultiValue(type, adapter)) { - Flux flux = Flux.from(publisher).materialize().flatMap(postAuthorize); + Flux flux = Flux.from(publisher).flatMap(postAuthorize); return (adapter != null) ? adapter.fromPublisher(flux) : flux; } - Mono mono = Mono.from(publisher).materialize().flatMap(postAuthorize); + Mono mono = Mono.from(publisher).flatMap(postAuthorize); return (adapter != null) ? adapter.fromPublisher(mono) : mono; } @@ -162,42 +148,9 @@ private boolean isMultiValue(Class returnType, ReactiveAdapter adapter) { return adapter != null && adapter.isMultiValue(); } - private Mono postAuthorize(Mono authentication, MethodInvocation mi, Object result) { - MethodInvocationResult invocationResult = new MethodInvocationResult(mi, result); - return this.authorizationManager.check(authentication, invocationResult) - .switchIfEmpty(Mono.just(new AuthorizationDecision(false))) - .flatMap((decision) -> postProcess(decision, invocationResult)); - } - - private Mono postProcess(AuthorizationResult decision, MethodInvocationResult methodInvocationResult) { - if (decision.isGranted()) { - return Mono.just(methodInvocationResult.getResult()); - } - return Mono.fromSupplier(() -> { - if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { - return handler.handleDeniedInvocationResult(methodInvocationResult, decision); - } - return this.defaultHandler.handleDeniedInvocationResult(methodInvocationResult, decision); - }).flatMap((processedResult) -> { - if (Mono.class.isAssignableFrom(processedResult.getClass())) { - return (Mono) processedResult; - } - return Mono.justOrEmpty(processedResult); - }); - } - - private Mono postProcess(AuthorizationResult decision, MethodInvocation methodInvocation) { - return Mono.fromSupplier(() -> { - if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { - return handler.handleDeniedInvocation(methodInvocation, decision); - } - return this.defaultHandler.handleDeniedInvocation(methodInvocation, decision); - }).flatMap((processedResult) -> { - if (Mono.class.isAssignableFrom(processedResult.getClass())) { - return (Mono) processedResult; - } - return Mono.justOrEmpty(processedResult); - }); + private Mono postAuthorize(Mono authentication, MethodInvocation mi, Object result) { + return this.authorizationManager.verify(authentication, new MethodInvocationResult(mi, result)) + .thenReturn(result); } @Override diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java index cb753e34cd9..0f38826d13b 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -28,16 +28,17 @@ import org.apache.commons.logging.LogFactory; import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.core.Ordered; import org.springframework.core.log.LogMessage; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; @@ -51,7 +52,8 @@ * @author Josh Cummings * @since 5.6 */ -public final class AuthorizationManagerBeforeMethodInterceptor implements AuthorizationAdvisor { +public final class AuthorizationManagerBeforeMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { private Supplier securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy; @@ -61,8 +63,6 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author private final AuthorizationManager authorizationManager; - private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler(); - private int order = AuthorizationInterceptorsOrder.FIRST.getOrder(); private AuthorizationEventPublisher eventPublisher = AuthorizationManagerBeforeMethodInterceptor::noPublish; @@ -194,7 +194,8 @@ public static AuthorizationManagerBeforeMethodInterceptor jsr250( */ @Override public Object invoke(MethodInvocation mi) throws Throwable { - return attemptAuthorization(mi); + attemptAuthorization(mi); + return mi.proceed(); } @Override @@ -245,49 +246,16 @@ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy secur this.securityContextHolderStrategy = () -> securityContextHolderStrategy; } - private Object attemptAuthorization(MethodInvocation mi) throws Throwable { + private void attemptAuthorization(MethodInvocation mi) { this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi)); - AuthorizationDecision decision; - try { - decision = this.authorizationManager.check(this::getAuthentication, mi); - } - catch (AuthorizationDeniedException denied) { - return handle(mi, denied); - } + AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, mi); this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, mi, decision); if (decision != null && !decision.isGranted()) { this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager " + this.authorizationManager + " and decision " + decision)); - return handle(mi, decision); + throw new AccessDeniedException("Access Denied"); } this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi)); - return proceed(mi); - } - - private Object proceed(MethodInvocation mi) throws Throwable { - try { - return mi.proceed(); - } - catch (AuthorizationDeniedException ex) { - if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { - return handler.handleDeniedInvocation(mi, ex); - } - return this.defaultHandler.handleDeniedInvocation(mi, ex); - } - } - - private Object handle(MethodInvocation mi, AuthorizationDeniedException denied) { - if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { - return handler.handleDeniedInvocation(mi, denied); - } - return this.defaultHandler.handleDeniedInvocation(mi, denied); - } - - private Object handle(MethodInvocation mi, AuthorizationResult decision) { - if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { - return handler.handleDeniedInvocation(mi, decision); - } - return this.defaultHandler.handleDeniedInvocation(mi, decision); } private Authentication getAuthentication() { diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java index ce9f94ae71b..f3d1cce8bfa 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -27,14 +27,14 @@ import reactor.core.publisher.Mono; import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; +import org.springframework.core.Ordered; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationDeniedException; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -48,7 +48,8 @@ * @author Josh Cummings * @since 5.8 */ -public final class AuthorizationManagerBeforeReactiveMethodInterceptor implements AuthorizationAdvisor { +public final class AuthorizationManagerBeforeReactiveMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow"; @@ -60,8 +61,6 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement private int order = AuthorizationInterceptorsOrder.FIRST.getOrder(); - private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler(); - /** * Creates an instance for the {@link PreAuthorize} annotation. * @return the {@link AuthorizationManagerBeforeReactiveMethodInterceptor} to use @@ -117,67 +116,31 @@ public Object invoke(MethodInvocation mi) throws Throwable { + " must return an instance of org.reactivestreams.Publisher " + "(for example, a Mono or Flux) or the function must be a Kotlin coroutine " + "in order to support Reactor Context"); + Mono authentication = ReactiveAuthenticationUtils.getAuthentication(); ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); + Mono preAuthorize = this.authorizationManager.verify(authentication, mi); if (hasFlowReturnType) { if (isSuspendingFunction) { - return preAuthorized(mi, Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi))); + return preAuthorize.thenMany(Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi))); } else { Assert.state(adapter != null, () -> "The returnType " + type + " on " + method + " must have a org.springframework.core.ReactiveAdapter registered"); - Flux response = preAuthorized(mi, - Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi)))); + Flux response = preAuthorize + .thenMany(Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi)))); return KotlinDelegate.asFlow(response); } } if (isMultiValue(type, adapter)) { - Flux result = preAuthorized(mi, Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi))); + Publisher publisher = Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); + Flux result = preAuthorize.thenMany(publisher); return (adapter != null) ? adapter.fromPublisher(result) : result; } - Mono result = preAuthorized(mi, Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi))); + Mono publisher = Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); + Mono result = preAuthorize.then(publisher); return (adapter != null) ? adapter.fromPublisher(result) : result; } - private Flux preAuthorized(MethodInvocation mi, Flux mapping) { - Mono authentication = ReactiveAuthenticationUtils.getAuthentication(); - return this.authorizationManager.check(authentication, mi) - .switchIfEmpty(Mono.just(new AuthorizationDecision(false))) - .flatMapMany((decision) -> { - if (decision.isGranted()) { - return mapping.onErrorResume(AuthorizationDeniedException.class, - (deniedEx) -> postProcess(deniedEx, mi)); - } - return postProcess(decision, mi); - }); - } - - private Mono preAuthorized(MethodInvocation mi, Mono mapping) { - Mono authentication = ReactiveAuthenticationUtils.getAuthentication(); - return this.authorizationManager.check(authentication, mi) - .switchIfEmpty(Mono.just(new AuthorizationDecision(false))) - .flatMap((decision) -> { - if (decision.isGranted()) { - return mapping.onErrorResume(AuthorizationDeniedException.class, - (deniedEx) -> postProcess(deniedEx, mi)); - } - return postProcess(decision, mi); - }); - } - - private Mono postProcess(AuthorizationResult decision, MethodInvocation mi) { - return Mono.fromSupplier(() -> { - if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { - return handler.handleDeniedInvocation(mi, decision); - } - return this.defaultHandler.handleDeniedInvocation(mi, decision); - }).flatMap((result) -> { - if (Mono.class.isAssignableFrom(result.getClass())) { - return (Mono) result; - } - return Mono.justOrEmpty(result); - }); - } - private boolean isMultiValue(Class returnType, ReactiveAdapter adapter) { if (Flux.class.isAssignableFrom(returnType)) { return true; diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizeReturnObject.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizeReturnObject.java deleted file mode 100644 index b9d3c4945eb..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizeReturnObject.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Wraps Spring Security method authorization advice around the return object of any - * method this annotation is applied to. - * - *

- * Placing this at the class level is semantically identical to placing it on each method - * in that class. - *

- * - * @author Josh Cummings - * @since 6.3 - * @see AuthorizeReturnObjectMethodInterceptor - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE, ElementType.METHOD }) -public @interface AuthorizeReturnObject { - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizeReturnObjectMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizeReturnObjectMethodInterceptor.java deleted file mode 100644 index cb06a59785b..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizeReturnObjectMethodInterceptor.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import java.lang.reflect.Method; -import java.util.function.Predicate; - -import org.aopalliance.aop.Advice; -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.aop.Pointcut; -import org.springframework.aop.support.Pointcuts; -import org.springframework.aop.support.StaticMethodMatcherPointcut; -import org.springframework.security.authorization.AuthorizationProxyFactory; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -/** - * A method interceptor that applies the given {@link AuthorizationProxyFactory} to any - * return value annotated with {@link AuthorizeReturnObject} - * - * @author Josh Cummings - * @since 6.3 - * @see AuthorizationAdvisorProxyFactory - */ -public final class AuthorizeReturnObjectMethodInterceptor implements AuthorizationAdvisor { - - private final AuthorizationProxyFactory authorizationProxyFactory; - - private Pointcut pointcut = Pointcuts.intersection( - new MethodReturnTypePointcut(Predicate.not(ClassUtils::isVoidType)), - AuthorizationMethodPointcuts.forAnnotations(AuthorizeReturnObject.class)); - - private int order = AuthorizationInterceptorsOrder.SECURE_RESULT.getOrder(); - - public AuthorizeReturnObjectMethodInterceptor(AuthorizationProxyFactory authorizationProxyFactory) { - Assert.notNull(authorizationProxyFactory, "authorizationManager cannot be null"); - this.authorizationProxyFactory = authorizationProxyFactory; - } - - @Override - public Object invoke(MethodInvocation mi) throws Throwable { - Object result = mi.proceed(); - if (result == null) { - return null; - } - return this.authorizationProxyFactory.proxy(result); - } - - @Override - public int getOrder() { - return this.order; - } - - public void setOrder(int order) { - this.order = order; - } - - /** - * {@inheritDoc} - */ - @Override - public Pointcut getPointcut() { - return this.pointcut; - } - - public void setPointcut(Pointcut pointcut) { - this.pointcut = pointcut; - } - - @Override - public Advice getAdvice() { - return this; - } - - @Override - public boolean isPerInstance() { - return true; - } - - static final class MethodReturnTypePointcut extends StaticMethodMatcherPointcut { - - private final Predicate> returnTypeMatches; - - MethodReturnTypePointcut(Predicate> returnTypeMatches) { - this.returnTypeMatches = returnTypeMatches; - } - - @Override - public boolean matches(Method method, Class targetClass) { - return this.returnTypeMatches.test(method.getReturnType()); - } - - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/ExpressionUtils.java b/core/src/main/java/org/springframework/security/authorization/method/ExpressionUtils.java deleted file mode 100644 index e0c0638257f..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/ExpressionUtils.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.EvaluationException; -import org.springframework.expression.Expression; -import org.springframework.security.authorization.AuthorizationResult; -import org.springframework.security.authorization.ExpressionAuthorizationDecision; - -final class ExpressionUtils { - - private ExpressionUtils() { - } - - static AuthorizationResult evaluate(Expression expr, EvaluationContext ctx) { - try { - Object result = expr.getValue(ctx); - if (result instanceof AuthorizationResult decision) { - return decision; - } - if (result instanceof Boolean granted) { - return new ExpressionAuthorizationDecision(granted, expr); - } - if (result == null) { - return null; - } - throw new IllegalArgumentException( - "SpEL expression must return either a Boolean or an AuthorizationDecision"); - } - catch (EvaluationException ex) { - throw new IllegalArgumentException("Failed to evaluate expression '" + expr.getExpressionString() + "'", - ex); - } - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java b/core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java deleted file mode 100644 index 7a28e9324ef..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation for specifying handling behavior when an authorization denied happens in - * method security or an - * {@link org.springframework.security.authorization.AuthorizationDeniedException} is - * thrown during method invocation - * - * @author Marcus da Coregio - * @since 6.3 - * @see AuthorizationManagerAfterMethodInterceptor - * @see AuthorizationManagerBeforeMethodInterceptor - */ -@Target({ ElementType.METHOD, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@Inherited -@Documented -public @interface HandleAuthorizationDenied { - - /** - * The {@link MethodAuthorizationDeniedHandler} used to handle denied authorization - * results - * @return - */ - Class handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class; - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java index f913db85f68..beb318ed15f 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -44,7 +44,6 @@ * * @author Evgeniy Cheban * @author Josh Cummings - * @author DingHao * @since 5.6 */ public final class Jsr250AuthorizationManager implements AuthorizationManager { @@ -122,8 +121,7 @@ AuthorizationManager resolveManager(Method method, Class ta private Annotation findJsr250Annotation(Method method, Class targetClass) { Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); Annotation annotation = findAnnotation(specificMethod); - return (annotation != null) ? annotation - : findAnnotation((targetClass != null) ? targetClass : specificMethod.getDeclaringClass()); + return (annotation != null) ? annotation : findAnnotation(specificMethod.getDeclaringClass()); } private Annotation findAnnotation(Method method) { diff --git a/core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedHandler.java b/core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedHandler.java deleted file mode 100644 index 5b059cf0b67..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedHandler.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.lang.Nullable; -import org.springframework.security.authorization.AuthorizationResult; - -/** - * An interface used to define a strategy to handle denied method invocations - * - * @author Marcus da Coregio - * @since 6.3 - * @see org.springframework.security.access.prepost.PreAuthorize - * @see org.springframework.security.access.prepost.PostAuthorize - */ -public interface MethodAuthorizationDeniedHandler { - - /** - * Handle denied method invocations, implementations might either throw an - * {@link org.springframework.security.authorization.AuthorizationDeniedException} or - * a replacement result instead of invoking the method, e.g. a masked value. - * @param methodInvocation the {@link MethodInvocation} related to the authorization - * denied - * @param authorizationResult the authorization denied result - * @return a replacement result for the denied method invocation, or null, or a - * {@link reactor.core.publisher.Mono} for reactive applications - */ - @Nullable - Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult); - - /** - * Handle denied method invocations, implementations might either throw an - * {@link org.springframework.security.authorization.AuthorizationDeniedException} or - * a replacement result instead of invoking the method, e.g. a masked value. By - * default, this method invokes - * {@link #handleDeniedInvocation(MethodInvocation, AuthorizationResult)}. - * @param methodInvocationResult the object containing the {@link MethodInvocation} - * and the result produced - * @param authorizationResult the authorization denied result - * @return a replacement result for the denied method invocation, or null, or a - * {@link reactor.core.publisher.Mono} for reactive applications - */ - @Nullable - default Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, - AuthorizationResult authorizationResult) { - return handleDeniedInvocation(methodInvocationResult.getMethodInvocation(), authorizationResult); - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java index 61953730366..0835a45acb3 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -20,13 +20,13 @@ import org.aopalliance.intercept.MethodInvocation; -import org.springframework.context.ApplicationContext; import org.springframework.expression.EvaluationContext; +import org.springframework.security.access.expression.ExpressionUtils; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.AuthorizationResult; +import org.springframework.security.authorization.ExpressionAuthorizationDecision; import org.springframework.security.core.Authentication; /** @@ -37,8 +37,7 @@ * @author Evgeniy Cheban * @since 5.6 */ -public final class PostAuthorizeAuthorizationManager - implements AuthorizationManager, MethodAuthorizationDeniedHandler { +public final class PostAuthorizeAuthorizationManager implements AuthorizationManager { private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); @@ -47,31 +46,7 @@ public final class PostAuthorizeAuthorizationManager * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); - } - - /** - * Configure pre/post-authorization template resolution - *

- * By default, this value is null, which indicates that templates should - * not be resolved. - * @param defaults - whether to resolve pre/post-authorization templates parameters - * @since 6.3 - */ - public void setTemplateDefaults(PrePostTemplateDefaults defaults) { - this.registry.setTemplateDefaults(defaults); - } - - /** - * Invokes - * {@link PostAuthorizeExpressionAttributeRegistry#setApplicationContext(ApplicationContext)} - * with the provided {@link ApplicationContext}. - * @param context the {@link ApplicationContext} - * @since 6.3 - * @see PreAuthorizeExpressionAttributeRegistry#setApplicationContext(ApplicationContext) - */ - public void setApplicationContext(ApplicationContext context) { - this.registry.setApplicationContext(context); + this.registry = new PostAuthorizeExpressionAttributeRegistry(expressionHandler); } /** @@ -92,23 +67,8 @@ public AuthorizationDecision check(Supplier authentication, Meth MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler(); EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation()); expressionHandler.setReturnObject(mi.getResult(), ctx); - return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx); - } - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation); - PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute; - return postAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult); - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, - AuthorizationResult authorizationResult) { - ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation()); - PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute; - return postAuthorizeAttribute.getHandler() - .handleDeniedInvocationResult(methodInvocationResult, authorizationResult); + boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx); + return new ExpressionAuthorizationDecision(granted, attribute.getExpression()); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttribute.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttribute.java deleted file mode 100644 index 3c3348ec246..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttribute.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import org.springframework.expression.Expression; -import org.springframework.util.Assert; - -/** - * An {@link ExpressionAttribute} that carries additional properties for - * {@code @PostAuthorize}. - * - * @author Marcus da Coregio - */ -class PostAuthorizeExpressionAttribute extends ExpressionAttribute { - - private final MethodAuthorizationDeniedHandler handler; - - PostAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedHandler handler) { - super(expression); - Assert.notNull(handler, "handler cannot be null"); - this.handler = handler; - } - - MethodAuthorizationDeniedHandler getHandler() { - return this.handler; - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java index 42b462ed03b..c89bbc3e312 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,16 +16,14 @@ package org.springframework.security.authorization.method; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.function.Function; import reactor.util.annotation.NonNull; import org.springframework.aop.support.AopUtils; -import org.springframework.context.ApplicationContext; import org.springframework.expression.Expression; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.util.Assert; @@ -33,76 +31,42 @@ * For internal use only, as this contract is likely to change. * * @author Evgeniy Cheban - * @author DingHao * @since 5.8 */ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler(); - - private Function, MethodAuthorizationDeniedHandler> handlerResolver; + private final MethodSecurityExpressionHandler expressionHandler; PostAuthorizeExpressionAttributeRegistry() { - this.handlerResolver = (clazz) -> this.defaultHandler; + this(new DefaultMethodSecurityExpressionHandler()); + } + + PostAuthorizeExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.expressionHandler = expressionHandler; + } + + MethodSecurityExpressionHandler getExpressionHandler() { + return this.expressionHandler; } @NonNull @Override ExpressionAttribute resolveAttribute(Method method, Class targetClass) { Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); - PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod, targetClass); + PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod); if (postAuthorize == null) { return ExpressionAttribute.NULL_ATTRIBUTE; } - Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value()); - MethodAuthorizationDeniedHandler deniedHandler = resolveHandler(method, targetClass); - return new PostAuthorizeExpressionAttribute(expression, deniedHandler); - } - - private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class targetClass) { - Function lookup = AuthorizationAnnotationUtils - .withDefaults(HandleAuthorizationDenied.class); - HandleAuthorizationDenied deniedHandler = lookup.apply(method); - if (deniedHandler != null) { - return this.handlerResolver.apply(deniedHandler.handlerClass()); - } - deniedHandler = lookup.apply(targetClass(method, targetClass)); - if (deniedHandler != null) { - return this.handlerResolver.apply(deniedHandler.handlerClass()); - } - return this.defaultHandler; - } - - private PostAuthorize findPostAuthorizeAnnotation(Method method, Class targetClass) { - Function lookup = findUniqueAnnotation(PostAuthorize.class); - PostAuthorize postAuthorize = lookup.apply(method); - return (postAuthorize != null) ? postAuthorize : lookup.apply(targetClass(method, targetClass)); - } - - /** - * Uses the provided {@link ApplicationContext} to resolve the - * {@link MethodAuthorizationDeniedPostProcessor} from {@link PostAuthorize} - * @param context the {@link ApplicationContext} to use - */ - void setApplicationContext(ApplicationContext context) { - Assert.notNull(context, "context cannot be null"); - this.handlerResolver = (clazz) -> resolveHandler(context, clazz); + Expression postAuthorizeExpression = this.expressionHandler.getExpressionParser() + .parseExpression(postAuthorize.value()); + return new ExpressionAttribute(postAuthorizeExpression); } - private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context, - Class handlerClass) { - if (handlerClass == this.defaultHandler.getClass()) { - return this.defaultHandler; - } - String[] beanNames = context.getBeanNamesForType(handlerClass); - if (beanNames.length == 0) { - throw new IllegalStateException("Could not find a bean of type " + handlerClass.getName()); - } - if (beanNames.length > 1) { - throw new IllegalStateException("Expected to find a single bean of type " + handlerClass.getName() - + " but found " + Arrays.toString(beanNames)); - } - return context.getBean(beanNames[0], handlerClass); + private PostAuthorize findPostAuthorizeAnnotation(Method method) { + PostAuthorize postAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostAuthorize.class); + return (postAuthorize != null) ? postAuthorize + : AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostAuthorize.class); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java index 475a7a2604e..214fa9654eb 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -19,12 +19,10 @@ import org.aopalliance.intercept.MethodInvocation; import reactor.core.publisher.Mono; -import org.springframework.context.ApplicationContext; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -38,9 +36,9 @@ * @since 5.8 */ public final class PostAuthorizeReactiveAuthorizationManager - implements ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { + implements ReactiveAuthorizationManager { - private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); + private final PostAuthorizeExpressionAttributeRegistry registry; public PostAuthorizeReactiveAuthorizationManager() { this(new DefaultMethodSecurityExpressionHandler()); @@ -48,23 +46,7 @@ public PostAuthorizeReactiveAuthorizationManager() { public PostAuthorizeReactiveAuthorizationManager(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.registry.setExpressionHandler(expressionHandler); - } - - /** - * Configure pre/post-authorization template resolution - *

- * By default, this value is null, which indicates that templates should - * not be resolved. - * @param defaults - whether to resolve pre/post-authorization templates parameters - * @since 6.3 - */ - public void setTemplateDefaults(PrePostTemplateDefaults defaults) { - this.registry.setTemplateDefaults(defaults); - } - - public void setApplicationContext(ApplicationContext context) { - this.registry.setApplicationContext(context); + this.registry = new PostAuthorizeExpressionAttributeRegistry(expressionHandler); } /** @@ -83,31 +65,14 @@ public Mono check(Mono authentication, Me if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) { return Mono.empty(); } - MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler(); // @formatter:off return authentication .map((auth) -> expressionHandler.createEvaluationContext(auth, mi)) .doOnNext((ctx) -> expressionHandler.setReturnObject(result.getResult(), ctx)) - .flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx)) - .cast(AuthorizationDecision.class); + .flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx)) + .map((granted) -> new ExpressionAttributeAuthorizationDecision(granted, attribute)); // @formatter:on } - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation); - PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute; - return postAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult); - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, - AuthorizationResult authorizationResult) { - ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation()); - PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute; - return postAuthorizeAttribute.getHandler() - .handleDeniedInvocationResult(methodInvocationResult, authorizationResult); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java index aa96de670da..99630298a23 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java @@ -23,6 +23,9 @@ import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.core.Ordered; import org.springframework.expression.EvaluationContext; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PostFilter; @@ -40,7 +43,8 @@ * @author Josh Cummings * @since 5.6 */ -public final class PostFilterAuthorizationMethodInterceptor implements AuthorizationAdvisor { +public final class PostFilterAuthorizationMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { private Supplier securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy; @@ -63,19 +67,7 @@ public PostFilterAuthorizationMethodInterceptor() { * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); - } - - /** - * Configure pre/post-authorization template resolution - *

- * By default, this value is null, which indicates that templates should - * not be resolved. - * @param defaults - whether to resolve pre/post-authorization templates parameters - * @since 6.3 - */ - public void setTemplateDefaults(PrePostTemplateDefaults defaults) { - this.registry.setTemplateDefaults(defaults); + this.registry = new PostFilterExpressionAttributeRegistry(expressionHandler); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java index 072bb5b75d5..c4eea895544 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java @@ -26,6 +26,9 @@ import reactor.core.publisher.Mono; import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.core.Ordered; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.expression.EvaluationContext; @@ -43,9 +46,10 @@ * @author Evgeniy Cheban * @since 5.8 */ -public final class PostFilterAuthorizationReactiveMethodInterceptor implements AuthorizationAdvisor { +public final class PostFilterAuthorizationReactiveMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { - private final PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry(); + private final PostFilterExpressionAttributeRegistry registry; private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class); @@ -63,19 +67,7 @@ public PostFilterAuthorizationReactiveMethodInterceptor() { */ public PostFilterAuthorizationReactiveMethodInterceptor(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.registry.setExpressionHandler(expressionHandler); - } - - /** - * Configure pre/post-authorization template resolution - *

- * By default, this value is null, which indicates that templates should - * not be resolved. - * @param defaults - whether to resolve pre/post-authorization templates parameters - * @since 6.3 - */ - public void setTemplateDefaults(PrePostTemplateDefaults defaults) { - this.registry.setTemplateDefaults(defaults); + this.registry = new PostFilterExpressionAttributeRegistry(expressionHandler); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java index 8b80c5101c6..4bc33bc493d 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,41 +16,56 @@ package org.springframework.security.authorization.method; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; -import java.util.function.Function; import org.springframework.aop.support.AopUtils; import org.springframework.expression.Expression; import org.springframework.lang.NonNull; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PostFilter; +import org.springframework.util.Assert; /** * For internal use only, as this contract is likely to change. * * @author Evgeniy Cheban - * @author DingHao * @since 5.8 */ final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { + private final MethodSecurityExpressionHandler expressionHandler; + + PostFilterExpressionAttributeRegistry() { + this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); + } + + PostFilterExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.expressionHandler = expressionHandler; + } + + MethodSecurityExpressionHandler getExpressionHandler() { + return this.expressionHandler; + } + @NonNull @Override ExpressionAttribute resolveAttribute(Method method, Class targetClass) { Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); - PostFilter postFilter = findPostFilterAnnotation(specificMethod, targetClass); + PostFilter postFilter = findPostFilterAnnotation(specificMethod); if (postFilter == null) { return ExpressionAttribute.NULL_ATTRIBUTE; } - Expression postFilterExpression = getExpressionHandler().getExpressionParser() + Expression postFilterExpression = this.expressionHandler.getExpressionParser() .parseExpression(postFilter.value()); return new ExpressionAttribute(postFilterExpression); } - private PostFilter findPostFilterAnnotation(Method method, Class targetClass) { - Function lookup = findUniqueAnnotation(PostFilter.class); - PostFilter postFilter = lookup.apply(method); - return (postFilter != null) ? postFilter : lookup.apply(targetClass(method, targetClass)); + private PostFilter findPostFilterAnnotation(Method method) { + PostFilter postFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostFilter.class); + return (postFilter != null) ? postFilter + : AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostFilter.class); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java index 02c26ebf34b..b58a3a7b7ef 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -20,13 +20,13 @@ import org.aopalliance.intercept.MethodInvocation; -import org.springframework.context.ApplicationContext; import org.springframework.expression.EvaluationContext; +import org.springframework.security.access.expression.ExpressionUtils; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.AuthorizationResult; +import org.springframework.security.authorization.ExpressionAuthorizationDecision; import org.springframework.security.core.Authentication; /** @@ -37,8 +37,7 @@ * @author Evgeniy Cheban * @since 5.6 */ -public final class PreAuthorizeAuthorizationManager - implements AuthorizationManager, MethodAuthorizationDeniedHandler { +public final class PreAuthorizeAuthorizationManager implements AuthorizationManager { private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry(); @@ -47,23 +46,7 @@ public final class PreAuthorizeAuthorizationManager * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); - } - - /** - * Configure pre/post-authorization template resolution - *

- * By default, this value is null, which indicates that templates should - * not be resolved. - * @param defaults - whether to resolve pre/post-authorization templates parameters - * @since 6.3 - */ - public void setTemplateDefaults(PrePostTemplateDefaults defaults) { - this.registry.setTemplateDefaults(defaults); - } - - public void setApplicationContext(ApplicationContext context) { - this.registry.setApplicationContext(context); + this.registry = new PreAuthorizeExpressionAttributeRegistry(expressionHandler); } /** @@ -82,14 +65,8 @@ public AuthorizationDecision check(Supplier authentication, Meth return null; } EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi); - return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx); - } - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation); - PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute; - return preAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult); + boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx); + return new ExpressionAuthorizationDecision(granted, attribute.getExpression()); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttribute.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttribute.java deleted file mode 100644 index 126e2404df4..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttribute.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import org.springframework.expression.Expression; -import org.springframework.util.Assert; - -/** - * An {@link ExpressionAttribute} that carries additional properties for - * {@code @PreAuthorize}. - * - * @author Marcus da Coregio - */ -class PreAuthorizeExpressionAttribute extends ExpressionAttribute { - - private final MethodAuthorizationDeniedHandler handler; - - PreAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedHandler handler) { - super(expression); - Assert.notNull(handler, "handler cannot be null"); - this.handler = handler; - } - - MethodAuthorizationDeniedHandler getHandler() { - return this.handler; - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java index 2bfe20a9324..dcae13eb205 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,16 +16,14 @@ package org.springframework.security.authorization.method; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.function.Function; import reactor.util.annotation.NonNull; import org.springframework.aop.support.AopUtils; -import org.springframework.context.ApplicationContext; import org.springframework.expression.Expression; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.util.Assert; @@ -33,76 +31,46 @@ * For internal use only, as this contract is likely to change. * * @author Evgeniy Cheban - * @author DingHao * @since 5.8 */ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler(); - - private Function, MethodAuthorizationDeniedHandler> handlerResolver; + private final MethodSecurityExpressionHandler expressionHandler; PreAuthorizeExpressionAttributeRegistry() { - this.handlerResolver = (clazz) -> this.defaultHandler; + this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); + } + + PreAuthorizeExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.expressionHandler = expressionHandler; + } + + /** + * Returns the {@link MethodSecurityExpressionHandler}. + * @return the {@link MethodSecurityExpressionHandler} to use + */ + MethodSecurityExpressionHandler getExpressionHandler() { + return this.expressionHandler; } @NonNull @Override ExpressionAttribute resolveAttribute(Method method, Class targetClass) { Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); - PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod, targetClass); + PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod); if (preAuthorize == null) { return ExpressionAttribute.NULL_ATTRIBUTE; } - Expression expression = getExpressionHandler().getExpressionParser().parseExpression(preAuthorize.value()); - MethodAuthorizationDeniedHandler handler = resolveHandler(method, targetClass); - return new PreAuthorizeExpressionAttribute(expression, handler); - } - - private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class targetClass) { - Function lookup = AuthorizationAnnotationUtils - .withDefaults(HandleAuthorizationDenied.class); - HandleAuthorizationDenied deniedHandler = lookup.apply(method); - if (deniedHandler != null) { - return this.handlerResolver.apply(deniedHandler.handlerClass()); - } - deniedHandler = lookup.apply(targetClass(method, targetClass)); - if (deniedHandler != null) { - return this.handlerResolver.apply(deniedHandler.handlerClass()); - } - return this.defaultHandler; - } - - private PreAuthorize findPreAuthorizeAnnotation(Method method, Class targetClass) { - Function lookup = findUniqueAnnotation(PreAuthorize.class); - PreAuthorize preAuthorize = lookup.apply(method); - return (preAuthorize != null) ? preAuthorize : lookup.apply(targetClass(method, targetClass)); - } - - /** - * Uses the provided {@link ApplicationContext} to resolve the - * {@link MethodAuthorizationDeniedHandler} from {@link PreAuthorize}. - * @param context the {@link ApplicationContext} to use - */ - void setApplicationContext(ApplicationContext context) { - Assert.notNull(context, "context cannot be null"); - this.handlerResolver = (clazz) -> resolveHandler(context, clazz); + Expression preAuthorizeExpression = this.expressionHandler.getExpressionParser() + .parseExpression(preAuthorize.value()); + return new ExpressionAttribute(preAuthorizeExpression); } - private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context, - Class handlerClass) { - if (handlerClass == this.defaultHandler.getClass()) { - return this.defaultHandler; - } - String[] beanNames = context.getBeanNamesForType(handlerClass); - if (beanNames.length == 0) { - throw new IllegalStateException("Could not find a bean of type " + handlerClass.getName()); - } - if (beanNames.length > 1) { - throw new IllegalStateException("Expected to find a single bean of type " + handlerClass.getName() - + " but found " + Arrays.toString(beanNames)); - } - return context.getBean(beanNames[0], handlerClass); + private PreAuthorize findPreAuthorizeAnnotation(Method method) { + PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class); + return (preAuthorize != null) ? preAuthorize + : AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreAuthorize.class); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java index cc768d861dd..cffdf832354 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -19,12 +19,10 @@ import org.aopalliance.intercept.MethodInvocation; import reactor.core.publisher.Mono; -import org.springframework.context.ApplicationContext; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -37,10 +35,9 @@ * @author Evgeniy Cheban * @since 5.8 */ -public final class PreAuthorizeReactiveAuthorizationManager - implements ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { +public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager { - private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry(); + private final PreAuthorizeExpressionAttributeRegistry registry; public PreAuthorizeReactiveAuthorizationManager() { this(new DefaultMethodSecurityExpressionHandler()); @@ -48,23 +45,7 @@ public PreAuthorizeReactiveAuthorizationManager() { public PreAuthorizeReactiveAuthorizationManager(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.registry.setExpressionHandler(expressionHandler); - } - - /** - * Configure pre/post-authorization template resolution - *

- * By default, this value is null, which indicates that templates should - * not be resolved. - * @param defaults - whether to resolve pre/post-authorization templates parameters - * @since 6.3 - */ - public void setTemplateDefaults(PrePostTemplateDefaults defaults) { - this.registry.setTemplateDefaults(defaults); - } - - public void setApplicationContext(ApplicationContext context) { - this.registry.setApplicationContext(context); + this.registry = new PreAuthorizeExpressionAttributeRegistry(expressionHandler); } /** @@ -84,16 +65,9 @@ public Mono check(Mono authentication, Me // @formatter:off return authentication .map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi)) - .flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx)) - .cast(AuthorizationDecision.class); + .flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx)) + .map((granted) -> new ExpressionAttributeAuthorizationDecision(granted, attribute)); // @formatter:on } - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation); - PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute; - return preAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java index a00e22f2534..bde5f1ee9d2 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java @@ -23,6 +23,9 @@ import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.core.Ordered; import org.springframework.expression.EvaluationContext; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PreFilter; @@ -41,7 +44,8 @@ * @author Josh Cummings * @since 5.6 */ -public final class PreFilterAuthorizationMethodInterceptor implements AuthorizationAdvisor { +public final class PreFilterAuthorizationMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { private Supplier securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy; @@ -64,19 +68,7 @@ public PreFilterAuthorizationMethodInterceptor() { * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); - } - - /** - * Configure pre/post-authorization template resolution - *

- * By default, this value is null, which indicates that templates should - * not be resolved. - * @param defaults - whether to resolve pre/post-authorization templates parameters - * @since 6.3 - */ - public void setTemplateDefaults(PrePostTemplateDefaults defaults) { - this.registry.setTemplateDefaults(defaults); + this.registry = new PreFilterExpressionAttributeRegistry(expressionHandler); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java index 19775eb126e..04c5d4b337e 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java @@ -26,7 +26,10 @@ import reactor.core.publisher.Mono; import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.aop.support.AopUtils; +import org.springframework.core.Ordered; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; @@ -47,9 +50,10 @@ * @author Evgeniy Cheban * @since 5.8 */ -public final class PreFilterAuthorizationReactiveMethodInterceptor implements AuthorizationAdvisor { +public final class PreFilterAuthorizationReactiveMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { - private final PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry(); + private final PreFilterExpressionAttributeRegistry registry; private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class); @@ -66,19 +70,7 @@ public PreFilterAuthorizationReactiveMethodInterceptor() { */ public PreFilterAuthorizationReactiveMethodInterceptor(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.registry.setExpressionHandler(expressionHandler); - } - - /** - * Configure pre/post-authorization template resolution - *

- * By default, this value is null, which indicates that templates should - * not be resolved. - * @param defaults - whether to resolve pre/post-authorization templates parameters - * @since 6.3 - */ - public void setTemplateDefaults(PrePostTemplateDefaults defaults) { - this.registry.setTemplateDefaults(defaults); + this.registry = new PreFilterExpressionAttributeRegistry(expressionHandler); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java index 95425c438dd..6fa8448355a 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,42 +16,57 @@ package org.springframework.security.authorization.method; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; -import java.util.function.Function; import org.springframework.aop.support.AopUtils; import org.springframework.expression.Expression; import org.springframework.lang.NonNull; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PreFilter; +import org.springframework.util.Assert; /** * For internal use only, as this contract is likely to change. * * @author Evgeniy Cheban - * @author DingHao * @since 5.8 */ final class PreFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { + private final MethodSecurityExpressionHandler expressionHandler; + + PreFilterExpressionAttributeRegistry() { + this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); + } + + PreFilterExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.expressionHandler = expressionHandler; + } + + MethodSecurityExpressionHandler getExpressionHandler() { + return this.expressionHandler; + } + @NonNull @Override PreFilterExpressionAttribute resolveAttribute(Method method, Class targetClass) { Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); - PreFilter preFilter = findPreFilterAnnotation(specificMethod, targetClass); + PreFilter preFilter = findPreFilterAnnotation(specificMethod); if (preFilter == null) { return PreFilterExpressionAttribute.NULL_ATTRIBUTE; } - Expression preFilterExpression = getExpressionHandler().getExpressionParser() + Expression preFilterExpression = this.expressionHandler.getExpressionParser() .parseExpression(preFilter.value()); return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget()); } - private PreFilter findPreFilterAnnotation(Method method, Class targetClass) { - Function lookup = findUniqueAnnotation(PreFilter.class); - PreFilter preFilter = lookup.apply(method); - return (preFilter != null) ? preFilter : lookup.apply(targetClass(method, targetClass)); + private PreFilter findPreFilterAnnotation(Method method) { + PreFilter preFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreFilter.class); + return (preFilter != null) ? preFilter + : AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreFilter.class); } static final class PreFilterExpressionAttribute extends ExpressionAttribute { diff --git a/core/src/main/java/org/springframework/security/authorization/method/PrePostTemplateDefaults.java b/core/src/main/java/org/springframework/security/authorization/method/PrePostTemplateDefaults.java deleted file mode 100644 index 7c309582c02..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/PrePostTemplateDefaults.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -/** - * A component for configuring various cross-cutting aspects of pre/post method security - * - * @author Josh Cummings - * @since 6.3 - * @see org.springframework.security.access.prepost.PreAuthorize - * @see org.springframework.security.access.prepost.PostAuthorize - * @see org.springframework.security.access.prepost.PreFilter - * @see org.springframework.security.access.prepost.PostFilter - */ -public final class PrePostTemplateDefaults { - - private boolean ignoreUnknown = true; - - /** - * Whether template resolution should ignore placeholders it doesn't recognize. - *

- * By default, this value is true. - * @since 6.3 - */ - public boolean isIgnoreUnknown() { - return this.ignoreUnknown; - } - - /** - * Configure template resolution to ignore unknown placeholders. When set to - * false, template resolution will throw an exception for unknown - * placeholders. - *

- * By default, this value is true. - * @param ignoreUnknown - whether to ignore unknown placeholders parameters - * @since 6.3 - */ - public void setIgnoreUnknown(boolean ignoreUnknown) { - this.ignoreUnknown = ignoreUnknown; - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/ReactiveExpressionUtils.java b/core/src/main/java/org/springframework/security/authorization/method/ReactiveExpressionUtils.java index 6c80c0d364e..2675bb96dc7 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/ReactiveExpressionUtils.java +++ b/core/src/main/java/org/springframework/security/authorization/method/ReactiveExpressionUtils.java @@ -21,8 +21,6 @@ import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; -import org.springframework.security.authorization.AuthorizationResult; -import org.springframework.security.authorization.ExpressionAuthorizationDecision; /** * For internal use only, as this contract is likely to change. @@ -32,33 +30,6 @@ */ final class ReactiveExpressionUtils { - static Mono evaluate(Expression expr, EvaluationContext ctx) { - return Mono.defer(() -> { - Object value; - try { - value = expr.getValue(ctx); - } - catch (EvaluationException ex) { - return Mono.error(() -> new IllegalArgumentException( - "Failed to evaluate expression '" + expr.getExpressionString() + "'", ex)); - } - if (value instanceof Mono mono) { - return mono.flatMap((data) -> adapt(expr, data)); - } - return adapt(expr, value); - }); - } - - private static Mono adapt(Expression expr, Object value) { - if (value instanceof Boolean granted) { - return Mono.just(new ExpressionAuthorizationDecision(granted, expr)); - } - if (value instanceof AuthorizationResult decision) { - return Mono.just(decision); - } - return createInvalidReturnTypeMono(expr); - } - static Mono evaluateAsBoolean(Expression expr, EvaluationContext ctx) { return Mono.defer(() -> { Object value; @@ -85,9 +56,9 @@ static Mono evaluateAsBoolean(Expression expr, EvaluationContext ctx) { }); } - private static Mono createInvalidReturnTypeMono(Expression expr) { - return Mono.error(() -> new IllegalStateException("Expression: '" + expr.getExpressionString() - + "' must return boolean, Mono, AuthorizationResult, or Mono")); + private static Mono createInvalidReturnTypeMono(Expression expr) { + return Mono.error(() -> new IllegalStateException( + "Expression: '" + expr.getExpressionString() + "' must return boolean or Mono")); } private ReactiveExpressionUtils() { diff --git a/core/src/main/java/org/springframework/security/authorization/method/SecuredAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/SecuredAuthorizationManager.java index 63553503d21..dcfc8a8511f 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/SecuredAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/SecuredAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -41,7 +41,6 @@ * contains a specified authority from the Spring Security's {@link Secured} annotation. * * @author Evgeniy Cheban - * @author DingHao * @since 5.6 */ public final class SecuredAuthorizationManager implements AuthorizationManager { @@ -87,14 +86,14 @@ private Set getAuthorities(MethodInvocation methodInvocation) { private Set resolveAuthorities(Method method, Class targetClass) { Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); - Secured secured = findSecuredAnnotation(specificMethod, targetClass); + Secured secured = findSecuredAnnotation(specificMethod); return (secured != null) ? Set.of(secured.value()) : Collections.emptySet(); } - private Secured findSecuredAnnotation(Method method, Class targetClass) { + private Secured findSecuredAnnotation(Method method) { Secured secured = AuthorizationAnnotationUtils.findUniqueAnnotation(method, Secured.class); - return (secured != null) ? secured : AuthorizationAnnotationUtils - .findUniqueAnnotation((targetClass != null) ? targetClass : method.getDeclaringClass(), Secured.class); + return (secured != null) ? secured + : AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), Secured.class); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedHandler.java b/core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedHandler.java deleted file mode 100644 index 3e4054a7389..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.security.authorization.AuthorizationDeniedException; -import org.springframework.security.authorization.AuthorizationResult; - -/** - * An implementation of {@link MethodAuthorizationDeniedHandler} that throws - * {@link AuthorizationDeniedException} - * - * @author Marcus da Coregio - * @since 6.3 - */ -public final class ThrowingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - if (authorizationResult instanceof AuthorizationDeniedException denied) { - throw denied; - } - throw new AuthorizationDeniedException("Access Denied", authorizationResult); - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, - AuthorizationResult authorizationResult) { - if (authorizationResult instanceof AuthorizationDeniedException denied) { - throw denied; - } - throw new AuthorizationDeniedException("Access Denied", authorizationResult); - } - -} diff --git a/core/src/main/java/org/springframework/security/core/ComparableVersion.java b/core/src/main/java/org/springframework/security/core/ComparableVersion.java index 347644734c3..a7477a4a5be 100644 --- a/core/src/main/java/org/springframework/security/core/ComparableVersion.java +++ b/core/src/main/java/org/springframework/security/core/ComparableVersion.java @@ -66,9 +66,9 @@ */ class ComparableVersion implements Comparable { - private static final int MAX_INT_ITEM_LENGTH = 9; + private static final int MAX_INTITEM_LENGTH = 9; - private static final int MAX_LONG_ITEM_LENGTH = 18; + private static final int MAX_LONGITEM_LENGTH = 18; private String value; @@ -559,11 +559,11 @@ else if (Character.isDigit(c)) { private static Item parseItem(boolean isDigit, String buf) { if (isDigit) { buf = stripLeadingZeroes(buf); - if (buf.length() <= MAX_INT_ITEM_LENGTH) { + if (buf.length() <= MAX_INTITEM_LENGTH) { // lower than 2^31 return new IntItem(buf); } - else if (buf.length() <= MAX_LONG_ITEM_LENGTH) { + else if (buf.length() <= MAX_LONGITEM_LENGTH) { // lower than 2^63 return new LongItem(buf); } diff --git a/core/src/main/java/org/springframework/security/core/SpringSecurityCoreVersion.java b/core/src/main/java/org/springframework/security/core/SpringSecurityCoreVersion.java index 186b80a89be..3d62aee5ad8 100644 --- a/core/src/main/java/org/springframework/security/core/SpringSecurityCoreVersion.java +++ b/core/src/main/java/org/springframework/security/core/SpringSecurityCoreVersion.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,9 @@ public final class SpringSecurityCoreVersion { /** * Global Serialization value for Spring Security classes. + * + * N.B. Classes are not intended to be serializable between different versions. See + * SEC-1709 for why we still need a serial version. */ public static final long SERIAL_VERSION_UID = 620L; diff --git a/core/src/main/java/org/springframework/security/core/session/InMemoryReactiveSessionRegistry.java b/core/src/main/java/org/springframework/security/core/session/InMemoryReactiveSessionRegistry.java deleted file mode 100644 index 8e0546568c2..00000000000 --- a/core/src/main/java/org/springframework/security/core/session/InMemoryReactiveSessionRegistry.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.core.session; - -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CopyOnWriteArraySet; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * Provides an in-memory implementation of {@link ReactiveSessionRegistry}. - * - * @author Marcus da Coregio - * @since 6.3 - */ -public class InMemoryReactiveSessionRegistry implements ReactiveSessionRegistry { - - private final ConcurrentMap> sessionIdsByPrincipal; - - private final Map sessionById; - - public InMemoryReactiveSessionRegistry() { - this.sessionIdsByPrincipal = new ConcurrentHashMap<>(); - this.sessionById = new ConcurrentHashMap<>(); - } - - public InMemoryReactiveSessionRegistry(ConcurrentMap> sessionIdsByPrincipal, - Map sessionById) { - this.sessionIdsByPrincipal = sessionIdsByPrincipal; - this.sessionById = sessionById; - } - - @Override - public Flux getAllSessions(Object principal) { - return Flux.fromIterable(this.sessionIdsByPrincipal.getOrDefault(principal, Collections.emptySet())) - .map(this.sessionById::get); - } - - @Override - public Mono saveSessionInformation(ReactiveSessionInformation information) { - this.sessionById.put(information.getSessionId(), information); - this.sessionIdsByPrincipal.computeIfAbsent(information.getPrincipal(), (key) -> new CopyOnWriteArraySet<>()) - .add(information.getSessionId()); - return Mono.empty(); - } - - @Override - public Mono getSessionInformation(String sessionId) { - return Mono.justOrEmpty(this.sessionById.get(sessionId)); - } - - @Override - public Mono removeSessionInformation(String sessionId) { - return getSessionInformation(sessionId).doOnNext((sessionInformation) -> { - this.sessionById.remove(sessionId); - Set sessionsUsedByPrincipal = this.sessionIdsByPrincipal.get(sessionInformation.getPrincipal()); - if (sessionsUsedByPrincipal != null) { - sessionsUsedByPrincipal.remove(sessionId); - if (sessionsUsedByPrincipal.isEmpty()) { - this.sessionIdsByPrincipal.remove(sessionInformation.getPrincipal()); - } - } - }); - } - - @Override - public Mono updateLastAccessTime(String sessionId) { - ReactiveSessionInformation session = this.sessionById.get(sessionId); - if (session != null) { - return session.refreshLastRequest().thenReturn(session); - } - return Mono.empty(); - } - -} diff --git a/core/src/main/java/org/springframework/security/core/session/ReactiveSessionInformation.java b/core/src/main/java/org/springframework/security/core/session/ReactiveSessionInformation.java deleted file mode 100644 index 6ebfdb9141f..00000000000 --- a/core/src/main/java/org/springframework/security/core/session/ReactiveSessionInformation.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-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.core.session; - -import java.io.Serial; -import java.io.Serializable; -import java.time.Instant; - -import reactor.core.publisher.Mono; - -import org.springframework.security.core.SpringSecurityCoreVersion; -import org.springframework.util.Assert; - -public class ReactiveSessionInformation implements Serializable { - - @Serial - private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; - - private Instant lastAccessTime; - - private final Object principal; - - private final String sessionId; - - private boolean expired = false; - - public ReactiveSessionInformation(Object principal, String sessionId, Instant lastAccessTime) { - Assert.notNull(principal, "principal cannot be null"); - Assert.hasText(sessionId, "sessionId cannot be null"); - Assert.notNull(lastAccessTime, "lastAccessTime cannot be null"); - this.principal = principal; - this.sessionId = sessionId; - this.lastAccessTime = lastAccessTime; - } - - public ReactiveSessionInformation withSessionId(String sessionId) { - return new ReactiveSessionInformation(this.principal, sessionId, this.lastAccessTime); - } - - public Mono invalidate() { - return Mono.fromRunnable(() -> this.expired = true); - } - - public Mono refreshLastRequest() { - this.lastAccessTime = Instant.now(); - return Mono.empty(); - } - - public Instant getLastAccessTime() { - return this.lastAccessTime; - } - - public Object getPrincipal() { - return this.principal; - } - - public String getSessionId() { - return this.sessionId; - } - - public boolean isExpired() { - return this.expired; - } - - public void setLastAccessTime(Instant lastAccessTime) { - this.lastAccessTime = lastAccessTime; - } - -} diff --git a/core/src/main/java/org/springframework/security/core/session/ReactiveSessionRegistry.java b/core/src/main/java/org/springframework/security/core/session/ReactiveSessionRegistry.java deleted file mode 100644 index 60e4a02cc29..00000000000 --- a/core/src/main/java/org/springframework/security/core/session/ReactiveSessionRegistry.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.core.session; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * Maintains a registry of {@link ReactiveSessionInformation} instances. - * - * @author Marcus da Coregio - * @since 6.3 - */ -public interface ReactiveSessionRegistry { - - /** - * Gets all the known {@link ReactiveSessionInformation} instances for the specified - * principal. - * @param principal the principal - * @return the {@link ReactiveSessionInformation} instances associated with the - * principal - */ - Flux getAllSessions(Object principal); - - /** - * Saves the {@link ReactiveSessionInformation} - * @param information the {@link ReactiveSessionInformation} to save - * @return a {@link Mono} that completes when the session is saved - */ - Mono saveSessionInformation(ReactiveSessionInformation information); - - /** - * Gets the {@link ReactiveSessionInformation} for the specified session identifier. - * @param sessionId the session identifier - * @return the {@link ReactiveSessionInformation} for the session. - */ - Mono getSessionInformation(String sessionId); - - /** - * Removes the specified session from the registry. - * @param sessionId the session identifier - * @return a {@link Mono} that completes when the session is removed - */ - Mono removeSessionInformation(String sessionId); - - /** - * Updates the last accessed time of the {@link ReactiveSessionInformation} - * @param sessionId the session identifier - * @return a {@link Mono} that completes when the session is updated - */ - Mono updateLastAccessTime(String sessionId); - -} diff --git a/core/src/main/java/org/springframework/security/core/userdetails/UserDetails.java b/core/src/main/java/org/springframework/security/core/userdetails/UserDetails.java index bab08c5a2ac..664725631e2 100644 --- a/core/src/main/java/org/springframework/security/core/userdetails/UserDetails.java +++ b/core/src/main/java/org/springframework/security/core/userdetails/UserDetails.java @@ -67,18 +67,14 @@ public interface UserDetails extends Serializable { * @return true if the user's account is valid (ie non-expired), * false if no longer valid (ie expired) */ - default boolean isAccountNonExpired() { - return true; - } + boolean isAccountNonExpired(); /** * Indicates whether the user is locked or unlocked. A locked user cannot be * authenticated. * @return true if the user is not locked, false otherwise */ - default boolean isAccountNonLocked() { - return true; - } + boolean isAccountNonLocked(); /** * Indicates whether the user's credentials (password) has expired. Expired @@ -86,17 +82,13 @@ default boolean isAccountNonLocked() { * @return true if the user's credentials are valid (ie non-expired), * false if no longer valid (ie expired) */ - default boolean isCredentialsNonExpired() { - return true; - } + boolean isCredentialsNonExpired(); /** * Indicates whether the user is enabled or disabled. A disabled user cannot be * authenticated. * @return true if the user is enabled, false otherwise */ - default boolean isEnabled() { - return true; - } + boolean isEnabled(); } diff --git a/core/src/main/resources/org/springframework/security/messages_ca.properties b/core/src/main/resources/org/springframework/security/messages_ca.properties index fca74d4a4c8..fde3ed32c7c 100644 --- a/core/src/main/resources/org/springframework/security/messages_ca.properties +++ b/core/src/main/resources/org/springframework/security/messages_ca.properties @@ -28,11 +28,11 @@ DigestAuthenticationFilter.nonceExpired=El nonce ha expirat/ha arribat fora de t DigestAuthenticationFilter.nonceNotNumeric=El nonce token haur\u00eda d'haver produ\u00eft un token num\u00e8ric inicial, per\u00f2 era {0} DigestAuthenticationFilter.nonceNotTwoTokens=El nonce hauria de produ\u00efr dos tokens, i no {0} DigestAuthenticationFilter.usernameNotFound=No s'ha trobat el nom d'usuari {0} -ExceptionTranslationFilter.insufficientAuthentication=Per accedir a aquest recurs cal autenticaci\u00f3 completa +#ExceptionTranslationFilter.insufficientAuthentication=Full authentication is required to access this resource JdbcDaoImpl.noAuthority=L'usuari {0} no t\u00e9 GrantedAuthority JdbcDaoImpl.notFound=No s'ha trobat l'usuari {0} LdapAuthenticationProvider.badCredentials=Credencials err\u00f2nies -LdapAuthenticationProvider.badLdapConnection=Ha fallat la connexi\u00f3 al servidor LDAP +#LdapAuthenticationProvider.badLdapConnection=Connection to LDAP server failed LdapAuthenticationProvider.credentialsExpired=Les credencials d'usuari han expirat LdapAuthenticationProvider.disabled=L'usuari est\u00e0 deshabilitat LdapAuthenticationProvider.expired=El compte d'usuari ha expirat diff --git a/core/src/main/resources/org/springframework/security/messages_es_ES.properties b/core/src/main/resources/org/springframework/security/messages_es_ES.properties index e9c6c9c5f59..484f53094aa 100644 --- a/core/src/main/resources/org/springframework/security/messages_es_ES.properties +++ b/core/src/main/resources/org/springframework/security/messages_es_ES.properties @@ -32,7 +32,7 @@ ExceptionTranslationFilter.insufficientAuthentication=Para acceder a este recurs JdbcDaoImpl.noAuthority=Usuario {0} no tiene GrantedAuthority JdbcDaoImpl.notFound=Usuario {0} no encontrado LdapAuthenticationProvider.badCredentials=Credenciales err\u00F3neas -LdapAuthenticationProvider.badLdapConnection=Fall\u00F3 la conexi\u00F3n al servidor LDAP +#LdapAuthenticationProvider.badLdapConnection=Connection to LDAP server failed LdapAuthenticationProvider.credentialsExpired=Las credenciales del usuario han expirado LdapAuthenticationProvider.disabled=El usuario est\u00E1 deshabilitado LdapAuthenticationProvider.expired=La cuenta del usuario ha expirado diff --git a/core/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java b/core/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java index 2e504b06831..575a326f01b 100644 --- a/core/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java +++ b/core/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -25,7 +25,7 @@ @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasRole('USER')") -@RolesAllowed("USER") +@RolesAllowed("ADMIN") @Secured("USER") public @interface RequireUserRole { diff --git a/core/src/test/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyImplTests.java b/core/src/test/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyImplTests.java index f6a48ea7a7e..c95024d6867 100644 --- a/core/src/test/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyImplTests.java +++ b/core/src/test/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyImplTests.java @@ -26,7 +26,6 @@ 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.assertThatNoException; /** @@ -206,99 +205,4 @@ public void singleLineLargeHierarchy() { .containsExactlyInAnyOrderElementsOf(allAuthorities); } - @Test - public void testFromHierarchyWithTextBlock() { - RoleHierarchyImpl roleHierarchyImpl = RoleHierarchyImpl.fromHierarchy(""" - ROLE_A > ROLE_B - ROLE_B > ROLE_C - ROLE_B > ROLE_D - """); - List flatAuthorities = AuthorityUtils.createAuthorityList("ROLE_A"); - List allAuthorities = AuthorityUtils.createAuthorityList("ROLE_A", "ROLE_B", "ROLE_C", - "ROLE_D"); - - assertThat(roleHierarchyImpl).isNotNull(); - assertThat(roleHierarchyImpl.getReachableGrantedAuthorities(flatAuthorities)) - .containsExactlyInAnyOrderElementsOf(allAuthorities); - } - - @Test - public void testFromHierarchyNoCycles() { - assertThatNoException().isThrownBy(() -> RoleHierarchyImpl - .fromHierarchy("ROLE_A > ROLE_B\nROLE_A > ROLE_C\nROLE_C > ROLE_D\nROLE_B > ROLE_D")); - } - - @Test - public void testFromHierarchyCycles() { - assertThatExceptionOfType(CycleInRoleHierarchyException.class) - .isThrownBy(() -> RoleHierarchyImpl.fromHierarchy("ROLE_A > ROLE_A")); - assertThatExceptionOfType(CycleInRoleHierarchyException.class) - .isThrownBy(() -> RoleHierarchyImpl.fromHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_A")); - assertThatExceptionOfType(CycleInRoleHierarchyException.class) - .isThrownBy(() -> RoleHierarchyImpl.fromHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_C\nROLE_C > ROLE_A")); - assertThatExceptionOfType(CycleInRoleHierarchyException.class).isThrownBy(() -> RoleHierarchyImpl - .fromHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_C\nROLE_C > ROLE_E\nROLE_E > ROLE_D\nROLE_D > ROLE_B")); - assertThatExceptionOfType(CycleInRoleHierarchyException.class) - .isThrownBy(() -> RoleHierarchyImpl.fromHierarchy("ROLE_C > ROLE_B\nROLE_B > ROLE_A\nROLE_A > ROLE_B")); - } - - @Test - public void testBuilderWithDefaultRolePrefix() { - RoleHierarchyImpl roleHierarchyImpl = RoleHierarchyImpl.withDefaultRolePrefix() - .role("A") - .implies("B") - .role("B") - .implies("C", "D") - .build(); - List flatAuthorities = AuthorityUtils.createAuthorityList("ROLE_A"); - List allAuthorities = AuthorityUtils.createAuthorityList("ROLE_A", "ROLE_B", "ROLE_C", - "ROLE_D"); - - assertThat(roleHierarchyImpl).isNotNull(); - assertThat(roleHierarchyImpl.getReachableGrantedAuthorities(flatAuthorities)) - .containsExactlyInAnyOrderElementsOf(allAuthorities); - } - - @Test - public void testBuilderWithRolePrefix() { - RoleHierarchyImpl roleHierarchyImpl = RoleHierarchyImpl.withRolePrefix("CUSTOM_PREFIX_") - .role("A") - .implies("B") - .build(); - List flatAuthorities = AuthorityUtils.createAuthorityList("CUSTOM_PREFIX_A"); - List allAuthorities = AuthorityUtils.createAuthorityList("CUSTOM_PREFIX_A", - "CUSTOM_PREFIX_B"); - - assertThat(roleHierarchyImpl).isNotNull(); - assertThat(roleHierarchyImpl.getReachableGrantedAuthorities(flatAuthorities)) - .containsExactlyInAnyOrderElementsOf(allAuthorities); - } - - @Test - public void testBuilderThrowIllegalArgumentExceptionWhenPrefixRoleNull() { - assertThatIllegalArgumentException().isThrownBy(() -> RoleHierarchyImpl.withRolePrefix(null)); - } - - @Test - public void testBuilderThrowIllegalArgumentExceptionWhenRoleEmpty() { - assertThatIllegalArgumentException().isThrownBy(() -> RoleHierarchyImpl.withDefaultRolePrefix().role("")); - } - - @Test - public void testBuilderThrowIllegalArgumentExceptionWhenRoleNull() { - assertThatIllegalArgumentException().isThrownBy(() -> RoleHierarchyImpl.withDefaultRolePrefix().role(null)); - } - - @Test - public void testBuilderThrowIllegalArgumentExceptionWhenImpliedRolesNull() { - assertThatIllegalArgumentException() - .isThrownBy(() -> RoleHierarchyImpl.withDefaultRolePrefix().role("A").implies((String) null)); - } - - @Test - public void testBuilderThrowIllegalArgumentExceptionWhenImpliedRolesEmpty() { - assertThatIllegalArgumentException() - .isThrownBy(() -> RoleHierarchyImpl.withDefaultRolePrefix().role("A").implies()); - } - } diff --git a/core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java b/core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java index 2d4b2c7a158..dd89bd7c89e 100644 --- a/core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,43 +77,4 @@ public void authenticateWhenBadCredentialsThenDelegate2NotInvokedAndError() { .verify(); } - @Test - public void authenticateWhenContinueOnErrorAndFirstBadCredentialsThenTriesSecond() { - given(this.delegate1.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test"))); - given(this.delegate2.authenticate(any())).willReturn(Mono.just(this.authentication)); - - DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError(); - - assertThat(manager.authenticate(this.authentication).block()).isEqualTo(this.authentication); - } - - @Test - public void authenticateWhenContinueOnErrorAndBothDelegatesBadCredentialsThenError() { - given(this.delegate1.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test"))); - given(this.delegate2.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test"))); - - DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError(); - - StepVerifier.create(manager.authenticate(this.authentication)) - .expectError(BadCredentialsException.class) - .verify(); - } - - @Test - public void authenticateWhenContinueOnErrorAndDelegate1NotEmptyThenReturnsNotEmpty() { - given(this.delegate1.authenticate(any())).willReturn(Mono.just(this.authentication)); - - DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError(); - - assertThat(manager.authenticate(this.authentication).block()).isEqualTo(this.authentication); - } - - private DelegatingReactiveAuthenticationManager managerWithContinueOnError() { - DelegatingReactiveAuthenticationManager manager = new DelegatingReactiveAuthenticationManager(this.delegate1, - this.delegate2); - manager.setContinueOnError(true); - - return manager; - } - } diff --git a/core/src/test/java/org/springframework/security/authentication/TestAuthentication.java b/core/src/test/java/org/springframework/security/authentication/TestAuthentication.java index 41cfcdf4ebd..a0faebee71c 100644 --- a/core/src/test/java/org/springframework/security/authentication/TestAuthentication.java +++ b/core/src/test/java/org/springframework/security/authentication/TestAuthentication.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,12 +16,9 @@ package org.springframework.security.authentication; -import java.util.function.Consumer; - import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; /** @@ -38,20 +35,14 @@ public class TestAuthentication extends PasswordEncodedUser { AuthorityUtils.createAuthorityList("ROLE_USER")); public static Authentication authenticatedAdmin() { - return authenticated(admin()); + return autheticated(admin()); } public static Authentication authenticatedUser() { - return authenticated(user()); - } - - public static Authentication authenticatedUser(Consumer consumer) { - User.UserBuilder builder = withUsername("user"); - consumer.accept(builder); - return authenticated(builder.build()); + return autheticated(user()); } - public static Authentication authenticated(UserDetails user) { + public static Authentication autheticated(UserDetails user) { return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities()); } diff --git a/core/src/test/java/org/springframework/security/authentication/UserDetailsRepositoryReactiveAuthenticationManagerTests.java b/core/src/test/java/org/springframework/security/authentication/UserDetailsRepositoryReactiveAuthenticationManagerTests.java index c386bdafaab..d2680e3624f 100644 --- a/core/src/test/java/org/springframework/security/authentication/UserDetailsRepositoryReactiveAuthenticationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authentication/UserDetailsRepositoryReactiveAuthenticationManagerTests.java @@ -24,12 +24,8 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; -import reactor.test.StepVerifier; import org.springframework.context.MessageSource; -import org.springframework.security.authentication.password.CompromisedPasswordCheckResult; -import org.springframework.security.authentication.password.CompromisedPasswordException; -import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; @@ -38,7 +34,6 @@ import org.springframework.security.core.userdetails.UserDetailsChecker; import org.springframework.security.crypto.password.PasswordEncoder; -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; @@ -224,41 +219,6 @@ public void authenticateWhenAccountDisabledThenException() { assertThatExceptionOfType(DisabledException.class).isThrownBy(() -> this.manager.authenticate(token).block()); } - @Test - public void authenticateWhenPasswordCompromisedThenException() { - // @formatter:off - UserDetails user = User.withUsername("user") - .password("{noop}password") - .roles("USER") - .build(); - // @formatter:on - given(this.userDetailsService.findByUsername(any())).willReturn(Mono.just(user)); - this.manager.setCompromisedPasswordChecker(new TestReactivePasswordChecker()); - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(user, - "password"); - StepVerifier.create(this.manager.authenticate(token)) - .expectErrorSatisfies((ex) -> assertThat(ex).isInstanceOf(CompromisedPasswordException.class) - .withFailMessage("The provided password is compromised, please change your password")) - .verify(); - } - - @Test - public void authenticateWhenPasswordNotCompromisedThenSuccess() { - // @formatter:off - UserDetails user = User.withUsername("user") - .password("{noop}notcompromised") - .roles("USER") - .build(); - // @formatter:on - given(this.userDetailsService.findByUsername(any())).willReturn(Mono.just(user)); - this.manager.setCompromisedPasswordChecker(new TestReactivePasswordChecker()); - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(user, - "notcompromised"); - StepVerifier.create(this.manager.authenticate(token)) - .assertNext((authentication) -> assertThat(authentication.getPrincipal()).isEqualTo(user)) - .verifyComplete(); - } - @Test public void setMessageSourceWhenNullThenThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.manager.setMessageSource(null)); @@ -273,16 +233,4 @@ public void setMessageSourceWhenNotNullThenCanGet() { verify(source).getMessage(eq(code), any(), any()); } - static class TestReactivePasswordChecker implements ReactiveCompromisedPasswordChecker { - - @Override - public Mono check(String password) { - if ("password".equals(password)) { - return Mono.just(new CompromisedPasswordCheckResult(true)); - } - return Mono.just(new CompromisedPasswordCheckResult(false)); - } - - } - } diff --git a/core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java b/core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java index 4e44f329a56..08a1c4dde36 100644 --- a/core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java +++ b/core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java @@ -33,9 +33,6 @@ import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authentication.password.CompromisedPasswordCheckResult; -import org.springframework.security.authentication.password.CompromisedPasswordChecker; -import org.springframework.security.authentication.password.CompromisedPasswordException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; @@ -51,7 +48,6 @@ 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.provisioning.InMemoryUserDetailsManager; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -508,42 +504,6 @@ public void testUserNotFoundNullCredentials() { verify(encoder, times(0)).matches(anyString(), anyString()); } - @Test - void authenticateWhenPasswordLeakedThenException() { - DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); - provider.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()); - UserDetails user = User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build(); - provider.setUserDetailsService(withUsers(user)); - provider.setCompromisedPasswordChecker(new TestCompromisedPasswordChecker()); - assertThatExceptionOfType(CompromisedPasswordException.class).isThrownBy( - () -> provider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("user", "password"))) - .withMessage("The provided password is compromised, please change your password"); - } - - @Test - void authenticateWhenPasswordNotLeakedThenNoException() { - DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); - provider.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()); - UserDetails user = User.withDefaultPasswordEncoder() - .username("user") - .password("strongpassword") - .roles("USER") - .build(); - provider.setUserDetailsService(withUsers(user)); - provider.setCompromisedPasswordChecker(new TestCompromisedPasswordChecker()); - Authentication authentication = provider - .authenticate(UsernamePasswordAuthenticationToken.unauthenticated("user", "strongpassword")); - assertThat(authentication).isNotNull(); - } - - private UserDetailsService withUsers(UserDetails... users) { - return new InMemoryUserDetailsManager(users); - } - private DaoAuthenticationProvider createProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance()); @@ -634,16 +594,4 @@ public UserDetails loadUserByUsername(String username) { } - private static class TestCompromisedPasswordChecker implements CompromisedPasswordChecker { - - @Override - public CompromisedPasswordCheckResult check(String password) { - if ("password".equals(password)) { - return new CompromisedPasswordCheckResult(true); - } - return new CompromisedPasswordCheckResult(false); - } - - } - } diff --git a/core/src/test/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactoryTests.java b/core/src/test/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactoryTests.java deleted file mode 100644 index 15389c2ec2c..00000000000 --- a/core/src/test/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactoryTests.java +++ /dev/null @@ -1,422 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Queue; -import java.util.Set; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.Test; - -import org.springframework.aop.Pointcut; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authentication.TestAuthentication; -import org.springframework.security.authorization.method.AuthorizationAdvisor; -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class AuthorizationAdvisorProxyFactoryTests { - - private final Authentication user = TestAuthentication.authenticatedUser(); - - private final Authentication admin = TestAuthentication.authenticatedAdmin(); - - private final Flight flight = new Flight(); - - private final User alan = new User("alan", "alan", "turing"); - - @Test - public void proxyWhenPreAuthorizeThenHonors() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Flight flight = new Flight(); - assertThat(flight.getAltitude()).isEqualTo(35000d); - Flight secured = proxy(factory, flight); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured::getAltitude); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenPreAuthorizeOnInterfaceThenHonors() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - assertThat(this.alan.getFirstName()).isEqualTo("alan"); - User secured = proxy(factory, this.alan); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured::getFirstName); - SecurityContextHolder.getContext().setAuthentication(authenticated("alan")); - assertThat(secured.getFirstName()).isEqualTo("alan"); - SecurityContextHolder.getContext().setAuthentication(this.admin); - assertThat(secured.getFirstName()).isEqualTo("alan"); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenPreAuthorizeOnRecordThenHonors() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - HasSecret repo = new Repository("secret"); - assertThat(repo.secret()).isEqualTo("secret"); - HasSecret secured = proxy(factory, repo); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured::secret); - SecurityContextHolder.getContext().setAuthentication(this.user); - assertThat(repo.secret()).isEqualTo("secret"); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenImmutableListThenReturnsSecuredImmutableList() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - List flights = List.of(this.flight); - List secured = proxy(factory, flights); - secured.forEach( - (flight) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude)); - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(secured::clear); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenImmutableSetThenReturnsSecuredImmutableSet() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Set flights = Set.of(this.flight); - Set secured = proxy(factory, flights); - secured.forEach( - (flight) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude)); - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(secured::clear); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenQueueThenReturnsSecuredQueue() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Queue flights = new LinkedList<>(List.of(this.flight)); - Queue secured = proxy(factory, flights); - assertThat(flights.size()).isEqualTo(secured.size()); - secured.forEach( - (flight) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude)); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenImmutableSortedSetThenReturnsSecuredImmutableSortedSet() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - SortedSet users = Collections.unmodifiableSortedSet(new TreeSet<>(Set.of(this.alan))); - SortedSet secured = proxy(factory, users); - secured - .forEach((user) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(user::getFirstName)); - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(secured::clear); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenImmutableSortedMapThenReturnsSecuredImmutableSortedMap() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - SortedMap users = Collections - .unmodifiableSortedMap(new TreeMap<>(Map.of(this.alan.getId(), this.alan))); - SortedMap secured = proxy(factory, users); - secured.forEach( - (id, user) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(user::getFirstName)); - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(secured::clear); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenImmutableMapThenReturnsSecuredImmutableMap() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Map users = Map.of(this.alan.getId(), this.alan); - Map secured = proxy(factory, users); - secured.forEach( - (id, user) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(user::getFirstName)); - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(secured::clear); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenMutableListThenReturnsSecuredMutableList() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - List flights = new ArrayList<>(List.of(this.flight)); - List secured = proxy(factory, flights); - secured.forEach( - (flight) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude)); - secured.clear(); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenMutableSetThenReturnsSecuredMutableSet() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Set flights = new HashSet<>(Set.of(this.flight)); - Set secured = proxy(factory, flights); - secured.forEach( - (flight) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude)); - secured.clear(); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenMutableSortedSetThenReturnsSecuredMutableSortedSet() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - SortedSet users = new TreeSet<>(Set.of(this.alan)); - SortedSet secured = proxy(factory, users); - secured.forEach((u) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(u::getFirstName)); - secured.clear(); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenMutableSortedMapThenReturnsSecuredMutableSortedMap() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - SortedMap users = new TreeMap<>(Map.of(this.alan.getId(), this.alan)); - SortedMap secured = proxy(factory, users); - secured.forEach((id, u) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(u::getFirstName)); - secured.clear(); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenMutableMapThenReturnsSecuredMutableMap() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Map users = new HashMap<>(Map.of(this.alan.getId(), this.alan)); - Map secured = proxy(factory, users); - secured.forEach((id, u) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(u::getFirstName)); - secured.clear(); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenPreAuthorizeForOptionalThenHonors() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Optional flights = Optional.of(this.flight); - assertThat(flights.get().getAltitude()).isEqualTo(35000d); - Optional secured = proxy(factory, flights); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.ifPresent(Flight::getAltitude)); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenPreAuthorizeForSupplierThenHonors() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Supplier flights = () -> this.flight; - assertThat(flights.get().getAltitude()).isEqualTo(35000d); - Supplier secured = proxy(factory, flights); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.get().getAltitude()); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenPreAuthorizeForStreamThenHonors() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Stream flights = Stream.of(this.flight); - Stream secured = proxy(factory, flights); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.forEach(Flight::getAltitude)); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenPreAuthorizeForArrayThenHonors() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Flight[] flights = { this.flight }; - Flight[] secured = proxy(factory, flights); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured[0]::getAltitude); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenPreAuthorizeForIteratorThenHonors() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Iterator flights = List.of(this.flight).iterator(); - Iterator secured = proxy(factory, flights); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.next().getAltitude()); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenPreAuthorizeForIterableThenHonors() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Iterable users = new UserRepository(); - Iterable secured = proxy(factory, users); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.forEach(User::getFirstName)); - SecurityContextHolder.clearContext(); - } - - @Test - public void proxyWhenPreAuthorizeForClassThenHonors() { - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - Class clazz = proxy(factory, Flight.class); - assertThat(clazz.getSimpleName()).contains("SpringCGLIB$$"); - Flight secured = proxy(factory, this.flight); - assertThat(secured.getClass()).isSameAs(clazz); - SecurityContextHolder.getContext().setAuthentication(this.user); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured::getAltitude); - SecurityContextHolder.clearContext(); - } - - @Test - public void setAdvisorsWhenProxyThenVisits() { - AuthorizationAdvisor advisor = mock(AuthorizationAdvisor.class); - given(advisor.getAdvice()).willReturn(advisor); - given(advisor.getPointcut()).willReturn(Pointcut.TRUE); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - factory.setAdvisors(advisor); - Flight flight = proxy(factory, this.flight); - flight.getAltitude(); - verify(advisor, atLeastOnce()).getPointcut(); - } - - @Test - public void setTargetVisitorThenUses() { - TargetVisitor visitor = mock(TargetVisitor.class); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - factory.setTargetVisitor(visitor); - factory.proxy(new Flight()); - verify(visitor).visit(any(), any()); - } - - @Test - public void setTargetVisitorIgnoreValueTypesThenIgnores() { - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> ((Integer) factory.proxy(35)).intValue()); - factory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes()); - assertThat(factory.proxy(35)).isEqualTo(35); - } - - private Authentication authenticated(String user, String... authorities) { - return TestAuthentication.authenticated(TestAuthentication.withUsername(user).authorities(authorities).build()); - } - - private T proxy(AuthorizationProxyFactory factory, Object target) { - return (T) factory.proxy(target); - } - - static class Flight { - - @PreAuthorize("hasRole('PILOT')") - Double getAltitude() { - return 35000d; - } - - } - - interface Identifiable { - - @PreAuthorize("authentication.name == this.id || hasRole('ADMIN')") - String getFirstName(); - - @PreAuthorize("authentication.name == this.id || hasRole('ADMIN')") - String getLastName(); - - } - - public static class User implements Identifiable, Comparable { - - private final String id; - - private final String firstName; - - private final String lastName; - - User(String id, String firstName, String lastName) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - } - - public String getId() { - return this.id; - } - - @Override - public String getFirstName() { - return this.firstName; - } - - @Override - public String getLastName() { - return this.lastName; - } - - @Override - public int compareTo(@NotNull User that) { - return this.id.compareTo(that.getId()); - } - - } - - static class UserRepository implements Iterable { - - List users = List.of(new User("1", "first", "last")); - - @NotNull - @Override - public Iterator iterator() { - return this.users.iterator(); - } - - } - - interface HasSecret { - - String secret(); - - } - - record Repository(@PreAuthorize("hasRole('ADMIN')") String secret) implements HasSecret { - } - -} diff --git a/core/src/test/java/org/springframework/security/authorization/AuthorizationManagersTests.java b/core/src/test/java/org/springframework/security/authorization/AuthorizationManagersTests.java index ba32fb63bbd..b05b2f41066 100644 --- a/core/src/test/java/org/springframework/security/authorization/AuthorizationManagersTests.java +++ b/core/src/test/java/org/springframework/security/authorization/AuthorizationManagersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -36,16 +36,6 @@ void checkAnyOfWhenOneGrantedThenGrantedDecision() { assertThat(decision.isGranted()).isTrue(); } - @Test - void checkAnyOfWithAllAbstainDefaultDecisionWhenOneGrantedThenGrantedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false); - AuthorizationManager composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision, - (a, o) -> new AuthorizationDecision(false), (a, o) -> new AuthorizationDecision(true)); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - // gh-13069 @Test void checkAnyOfWhenAllNonAbstainingDeniesThenDeniedDecision() { @@ -64,58 +54,6 @@ void checkAnyOfWhenEmptyThenDeniedDecision() { assertThat(decision.isGranted()).isFalse(); } - @Test - void checkAnyOfWithAllAbstainDefaultDecisionIsDeniedWhenEmptyThenDeniedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false); - AuthorizationManager composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isFalse(); - } - - @Test - void checkAnyOfWithAllAbstainDefaultDecisionIsGrantedWhenEmptyThenGrantedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true); - AuthorizationManager composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - void checkAnyOfWithAllAbstainDefaultDecisionIsAbstainWhenEmptyThenAbstainDecision() { - AuthorizationDecision allAbstainDefaultDecision = null; - AuthorizationManager composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNull(); - } - - @Test - void checkAnyOfWhenAllAbstainDefaultDecisionIsGrantedAndAllManagersAbstainThenGrantedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true); - AuthorizationManager composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision, (a, o) -> null); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - void checkAnyOfWhenAllAbstainDefaultDecisionIsDeniedAndAllManagersAbstainThenDeniedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false); - AuthorizationManager composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision, (a, o) -> null); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isFalse(); - } - - @Test - void checkAnyOfWhenAllAbstainDefaultDecisionIsAbstainAndAllManagersAbstainThenAbstainDecision() { - AuthorizationDecision allAbstainDefaultDecision = null; - AuthorizationManager composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision, (a, o) -> null); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNull(); - } - @Test void checkAllOfWhenAllGrantedThenGrantedDecision() { AuthorizationManager composed = AuthorizationManagers.allOf((a, o) -> new AuthorizationDecision(true), @@ -125,16 +63,6 @@ void checkAllOfWhenAllGrantedThenGrantedDecision() { assertThat(decision.isGranted()).isTrue(); } - @Test - void checkAllOfWithAllAbstainDefaultDecisionWhenAllGrantedThenGrantedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false); - AuthorizationManager composed = AuthorizationManagers.allOf(allAbstainDefaultDecision, - (a, o) -> new AuthorizationDecision(true), (a, o) -> new AuthorizationDecision(true)); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - // gh-13069 @Test void checkAllOfWhenAllNonAbstainingGrantsThenGrantedDecision() { @@ -154,16 +82,6 @@ void checkAllOfWhenOneDeniedThenDeniedDecision() { assertThat(decision.isGranted()).isFalse(); } - @Test - void checkAllOfWithAllAbstainDefaultDecisionWhenOneDeniedThenDeniedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true); - AuthorizationManager composed = AuthorizationManagers.allOf(allAbstainDefaultDecision, - (a, o) -> new AuthorizationDecision(true), (a, o) -> new AuthorizationDecision(false)); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isFalse(); - } - @Test void checkAllOfWhenEmptyThenGrantedDecision() { AuthorizationManager composed = AuthorizationManagers.allOf(); @@ -172,71 +90,4 @@ void checkAllOfWhenEmptyThenGrantedDecision() { assertThat(decision.isGranted()).isTrue(); } - @Test - void checkAllOfWithAllAbstainDefaultDecisionIsDeniedWhenEmptyThenDeniedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false); - AuthorizationManager composed = AuthorizationManagers.allOf(allAbstainDefaultDecision); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isFalse(); - } - - @Test - void checkAllOfWithAllAbstainDefaultDecisionIsGrantedWhenEmptyThenGrantedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true); - AuthorizationManager composed = AuthorizationManagers.allOf(allAbstainDefaultDecision); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - void checkAllOfWithAllAbstainDefaultDecisionIsAbstainWhenEmptyThenAbstainDecision() { - AuthorizationDecision allAbstainDefaultDecision = null; - AuthorizationManager composed = AuthorizationManagers.allOf(allAbstainDefaultDecision); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNull(); - } - - @Test - void checkAllOfWhenAllAbstainDefaultDecisionIsDeniedAndAllManagersAbstainThenDeniedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false); - AuthorizationManager composed = AuthorizationManagers.allOf(allAbstainDefaultDecision, (a, o) -> null); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isFalse(); - } - - @Test - void checkAllOfWhenAllAbstainDefaultDecisionIsGrantedAndAllManagersAbstainThenGrantedDecision() { - AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true); - AuthorizationManager composed = AuthorizationManagers.allOf(allAbstainDefaultDecision, (a, o) -> null); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - void checkAllOfWhenAllAbstainDefaultDecisionIsAbstainAndAllManagersAbstainThenAbstainDecision() { - AuthorizationDecision allAbstainDefaultDecision = null; - AuthorizationManager composed = AuthorizationManagers.allOf(allAbstainDefaultDecision, (a, o) -> null); - AuthorizationDecision decision = composed.check(null, null); - assertThat(decision).isNull(); - } - - @Test - void checkNotWhenEmptyThenAbstainedDecision() { - AuthorizationManager negated = AuthorizationManagers.not((a, o) -> null); - AuthorizationDecision decision = negated.check(null, null); - assertThat(decision).isNull(); - } - - @Test - void checkNotWhenGrantedThenDeniedDecision() { - AuthorizationManager negated = AuthorizationManagers.not((a, o) -> new AuthorizationDecision(true)); - AuthorizationDecision decision = negated.check(null, null); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isFalse(); - } - } diff --git a/core/src/test/java/org/springframework/security/authorization/ReactiveAuthorizationAdvisorProxyFactoryTests.java b/core/src/test/java/org/springframework/security/authorization/ReactiveAuthorizationAdvisorProxyFactoryTests.java deleted file mode 100644 index 901cae12519..00000000000 --- a/core/src/test/java/org/springframework/security/authorization/ReactiveAuthorizationAdvisorProxyFactoryTests.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization; - -import java.util.Iterator; -import java.util.List; - -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.aop.Pointcut; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authentication.TestAuthentication; -import org.springframework.security.authorization.method.AuthorizationAdvisor; -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class ReactiveAuthorizationAdvisorProxyFactoryTests { - - private final Authentication user = TestAuthentication.authenticatedUser(); - - private final Authentication admin = TestAuthentication.authenticatedAdmin(); - - private final Flight flight = new Flight(); - - private final User alan = new User("alan", "alan", "turing"); - - @Test - public void proxyWhenPreAuthorizeThenHonors() { - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults(); - Flight flight = new Flight(); - StepVerifier - .create(flight.getAltitude().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user))) - .expectNext(35000d) - .verifyComplete(); - Flight secured = proxy(factory, flight); - StepVerifier - .create(secured.getAltitude().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user))) - .verifyError(AccessDeniedException.class); - } - - @Test - public void proxyWhenPreAuthorizeOnInterfaceThenHonors() { - SecurityContextHolder.getContext().setAuthentication(this.user); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults(); - StepVerifier - .create(this.alan.getFirstName().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user))) - .expectNext("alan") - .verifyComplete(); - User secured = proxy(factory, this.alan); - StepVerifier - .create(secured.getFirstName().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user))) - .verifyError(AccessDeniedException.class); - StepVerifier - .create(secured.getFirstName() - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authenticated("alan")))) - .expectNext("alan") - .verifyComplete(); - StepVerifier - .create(secured.getFirstName().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.admin))) - .expectNext("alan") - .verifyComplete(); - } - - @Test - public void proxyWhenPreAuthorizeOnRecordThenHonors() { - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults(); - HasSecret repo = new Repository(Mono.just("secret")); - StepVerifier.create(repo.secret().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user))) - .expectNext("secret") - .verifyComplete(); - HasSecret secured = proxy(factory, repo); - StepVerifier.create(secured.secret().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user))) - .verifyError(AccessDeniedException.class); - StepVerifier.create(secured.secret().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.admin))) - .expectNext("secret") - .verifyComplete(); - } - - @Test - public void proxyWhenPreAuthorizeOnFluxThenHonors() { - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults(); - Flux flights = Flux.just(this.flight); - Flux secured = proxy(factory, flights); - StepVerifier - .create(secured.flatMap(Flight::getAltitude) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user))) - .verifyError(AccessDeniedException.class); - } - - @Test - public void proxyWhenPreAuthorizeForClassThenHonors() { - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults(); - Class clazz = proxy(factory, Flight.class); - assertThat(clazz.getSimpleName()).contains("SpringCGLIB$$"); - Flight secured = proxy(factory, this.flight); - StepVerifier - .create(secured.getAltitude().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user))) - .verifyError(AccessDeniedException.class); - } - - @Test - public void setAdvisorsWhenProxyThenVisits() { - AuthorizationAdvisor advisor = mock(AuthorizationAdvisor.class); - given(advisor.getAdvice()).willReturn(advisor); - given(advisor.getPointcut()).willReturn(Pointcut.TRUE); - AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults(); - factory.setAdvisors(advisor); - Flight flight = proxy(factory, this.flight); - flight.getAltitude(); - verify(advisor, atLeastOnce()).getPointcut(); - } - - private Authentication authenticated(String user, String... authorities) { - return TestAuthentication.authenticated(TestAuthentication.withUsername(user).authorities(authorities).build()); - } - - private T proxy(AuthorizationProxyFactory factory, Object target) { - return (T) factory.proxy(target); - } - - static class Flight { - - @PreAuthorize("hasRole('PILOT')") - Mono getAltitude() { - return Mono.just(35000d); - } - - } - - interface Identifiable { - - @PreAuthorize("authentication.name == this.id || hasRole('ADMIN')") - Mono getFirstName(); - - @PreAuthorize("authentication.name == this.id || hasRole('ADMIN')") - Mono getLastName(); - - } - - public static class User implements Identifiable, Comparable { - - private final String id; - - private final String firstName; - - private final String lastName; - - User(String id, String firstName, String lastName) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - } - - public String getId() { - return this.id; - } - - @Override - public Mono getFirstName() { - return Mono.just(this.firstName); - } - - @Override - public Mono getLastName() { - return Mono.just(this.lastName); - } - - @Override - public int compareTo(@NotNull User that) { - return this.id.compareTo(that.getId()); - } - - } - - static class UserRepository implements Iterable { - - List users = List.of(new User("1", "first", "last")); - - Flux findAll() { - return Flux.fromIterable(this.users); - } - - @NotNull - @Override - public Iterator iterator() { - return this.users.iterator(); - } - - } - - interface HasSecret { - - Mono secret(); - - } - - record Repository(@PreAuthorize("hasRole('ADMIN')") Mono secret) implements HasSecret { - } - -} diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtilsTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtilsTests.java index 3dfc24a9e3d..8c7cfc3ec8f 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtilsTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,26 +16,18 @@ package org.springframework.security.authorization.method; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import org.junit.jupiter.api.Test; -import org.springframework.core.annotation.AliasFor; -import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.security.access.prepost.PreAuthorize; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; /** - * Tests for {@link AuthorizationAnnotationUtils}. - * - * @author Josh Cummings - * @author Sam Brannen + * Tests for {@link AuthorizationAnnotationUtils} */ class AuthorizationAnnotationUtilsTests { @@ -45,56 +37,15 @@ void annotationsOnSyntheticMethodsShouldNotTriggerAnnotationConfigurationExcepti Thread.currentThread().getContextClassLoader(), new Class[] { StringRepository.class }, (p, m, args) -> null); Method method = proxy.getClass().getDeclaredMethod("findAll"); - PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class); - assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')"); + assertThatNoException() + .isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class)); } @Test // gh-13625 void annotationsFromSuperSuperInterfaceShouldNotTriggerAnnotationConfigurationException() throws Exception { - Method method = HelloImpl.class.getDeclaredMethod("sayHello"); - PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class); - assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')"); - } - - @Test - void multipleIdenticalAnnotationsOnClassShouldNotTriggerAnnotationConfigurationException() { - Class clazz = MultipleIdenticalPreAuthorizeAnnotationsOnClass.class; - PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, PreAuthorize.class); - assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')"); - } - - @Test - void multipleIdenticalAnnotationsOnMethodShouldNotTriggerAnnotationConfigurationException() throws Exception { - Method method = MultipleIdenticalPreAuthorizeAnnotationsOnMethod.class.getDeclaredMethod("method"); - PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class); - assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')"); - } - - @Test - void competingAnnotationsOnClassShouldTriggerAnnotationConfigurationException() { - Class clazz = CompetingPreAuthorizeAnnotationsOnClass.class; - assertThatExceptionOfType(AnnotationConfigurationException.class) - .isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, PreAuthorize.class)) - .withMessageContainingAll("Found 2 competing annotations:", "someRole", "otherRole"); - } - - @Test - void competingAnnotationsOnMethodShouldTriggerAnnotationConfigurationException() throws Exception { - Method method = CompetingPreAuthorizeAnnotationsOnMethod.class.getDeclaredMethod("method"); - assertThatExceptionOfType(AnnotationConfigurationException.class) - .isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class)) - .withMessageContainingAll("Found 2 competing annotations:", "someRole", "otherRole"); - } - - @Test - void composedMergedAnnotationsAreNotSupported() { - Class clazz = ComposedPreAuthAnnotationOnClass.class; - PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, PreAuthorize.class); - - // If you comment out .map(MergedAnnotation::withNonMergedAttributes) in - // AuthorizationAnnotationUtils.findDistinctAnnotation(), the value of - // the merged annotation would be "hasRole('composedRole')". - assertThat(preAuthorize.value()).isEqualTo("hasRole('metaRole')"); + Method method = HelloImpl.class.getMethod("sayHello"); + assertThatNoException() + .isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class)); } private interface BaseRepository { @@ -131,60 +82,4 @@ public String sayHello() { } - @Retention(RetentionPolicy.RUNTIME) - @PreAuthorize("hasRole('someRole')") - private @interface RequireSomeRole { - - } - - @Retention(RetentionPolicy.RUNTIME) - @PreAuthorize("hasRole('otherRole')") - private @interface RequireOtherRole { - - } - - @RequireSomeRole - @PreAuthorize("hasRole('someRole')") - private static class MultipleIdenticalPreAuthorizeAnnotationsOnClass { - - } - - private static class MultipleIdenticalPreAuthorizeAnnotationsOnMethod { - - @RequireSomeRole - @PreAuthorize("hasRole('someRole')") - void method() { - } - - } - - @RequireOtherRole - @PreAuthorize("hasRole('someRole')") - private static class CompetingPreAuthorizeAnnotationsOnClass { - - } - - private static class CompetingPreAuthorizeAnnotationsOnMethod { - - @RequireOtherRole - @PreAuthorize("hasRole('someRole')") - void method() { - } - - } - - @Retention(RetentionPolicy.RUNTIME) - @PreAuthorize("hasRole('metaRole')") - private @interface ComposedPreAuth { - - @AliasFor(annotation = PreAuthorize.class) - String value(); - - } - - @ComposedPreAuth("hasRole('composedRole')") - private static class ComposedPreAuthAnnotationOnClass { - - } - } diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java index e34b45a33cb..b82785f1017 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java @@ -26,10 +26,8 @@ import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.AuthenticatedAuthorizationManager; import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; @@ -38,7 +36,6 @@ import org.springframework.security.core.context.SecurityContextImpl; 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; @@ -142,24 +139,4 @@ public void invokeWhenAuthorizationEventPublisherThenUses() throws Throwable { any(AuthorizationDecision.class)); } - @Test - public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() throws Throwable { - MethodInvocation mi = mock(MethodInvocation.class); - given(mi.proceed()).willReturn("ok"); - AuthorizationManager manager = mock(AuthorizationManager.class); - given(manager.check(any(), any())) - .willThrow(new MyAuthzDeniedException("denied", new AuthorizationDecision(false))); - AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor( - Pointcut.TRUE, manager); - assertThatExceptionOfType(MyAuthzDeniedException.class).isThrownBy(() -> advice.invoke(mi)); - } - - static class MyAuthzDeniedException extends AuthorizationDeniedException { - - MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) { - super(msg, authorizationResult); - } - - } - } diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java index b18fb4fee62..572fd754f4f 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -19,16 +19,12 @@ import org.aopalliance.intercept.MethodInvocation; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; -import org.mockito.invocation.InvocationOnMock; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.aop.Pointcut; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationDeniedException; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ReactiveAuthorizationManager; import static org.assertj.core.api.Assertions.assertThat; @@ -70,15 +66,14 @@ public void invokeMonoWhenMockReactiveAuthorizationManagerThenVerify() throws Th given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), any())) - .willReturn(Mono.just(new AuthorizationDecision(true))); + given(mockReactiveAuthorizationManager.verify(any(), any())).willReturn(Mono.empty()); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) .extracting(Mono::block) .isEqualTo("john"); - verify(mockReactiveAuthorizationManager).check(any(), any()); + verify(mockReactiveAuthorizationManager).verify(any(), any()); } @Test @@ -88,8 +83,7 @@ public void invokeFluxWhenMockReactiveAuthorizationManagerThenVerify() throws Th given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), any())) - .willReturn(Mono.just(new AuthorizationDecision(true))); + given(mockReactiveAuthorizationManager.verify(any(), any())).willReturn(Mono.empty()); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -97,7 +91,7 @@ public void invokeFluxWhenMockReactiveAuthorizationManagerThenVerify() throws Th .extracting(Flux::collectList) .extracting(Mono::block, InstanceOfAssertFactories.list(String.class)) .containsExactly("john", "bob"); - verify(mockReactiveAuthorizationManager, times(2)).check(any(), any()); + verify(mockReactiveAuthorizationManager, times(2)).verify(any(), any()); } @Test @@ -107,8 +101,8 @@ public void invokeWhenMockReactiveAuthorizationManagerDeniedThenAccessDeniedExce given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), any())) - .willReturn(Mono.just(new AuthorizationDecision(false))); + given(mockReactiveAuthorizationManager.verify(any(), any())) + .willReturn(Mono.error(new AccessDeniedException("Access Denied"))); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -116,158 +110,7 @@ public void invokeWhenMockReactiveAuthorizationManagerDeniedThenAccessDeniedExce .isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) .extracting(Mono::block)) .withMessage("Access Denied"); - verify(mockReactiveAuthorizationManager).check(any(), any()); - } - - @Test - public void invokeFluxWhenAllValuesDeniedAndPostProcessorThenPostProcessorAppliedToEachValueEmitted() - throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); - given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class))) - .willAnswer(this::masking); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); - AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class)) - .extracting(Flux::collectList) - .extracting(Mono::block, InstanceOfAssertFactories.list(String.class)) - .containsExactly("john-masked", "bob-masked"); - verify(mockReactiveAuthorizationManager, times(2)).check(any(), any()); - } - - @Test - public void invokeFluxWhenOneValueDeniedAndPostProcessorThenPostProcessorAppliedToDeniedValue() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); - given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class))) - .willAnswer((invocation) -> { - MethodInvocationResult argument = invocation.getArgument(0); - if (!"john".equals(argument.getResult())) { - return monoMasking(invocation); - } - return Mono.just(argument.getResult()); - }); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); - AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class)) - .extracting(Flux::collectList) - .extracting(Mono::block, InstanceOfAssertFactories.list(String.class)) - .containsExactly("john", "bob-masked"); - verify(mockReactiveAuthorizationManager, times(2)).check(any(), any()); - } - - @Test - public void invokeMonoWhenPostProcessableDecisionThenPostProcess() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); - given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class))) - .willAnswer(this::masking); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); - AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) - .extracting(Mono::block) - .isEqualTo("john-masked"); - verify(mockReactiveAuthorizationManager).check(any(), any()); - } - - @Test - public void invokeMonoWhenPostProcessableDecisionAndPostProcessResultIsMonoThenPostProcessWorks() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); - given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class))) - .willAnswer(this::monoMasking); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); - AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) - .extracting(Mono::block) - .isEqualTo("john-masked"); - verify(mockReactiveAuthorizationManager).check(any(), any()); - } - - @Test - public void invokeMonoWhenPostProcessableDecisionAndPostProcessResultIsNullThenPostProcessWorks() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); - given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class))) - .willReturn(null); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); - AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) - .extracting(Mono::block) - .isEqualTo(null); - verify(mockReactiveAuthorizationManager).check(any(), any()); - } - - @Test - public void invokeMonoWhenEmptyDecisionThenUseDefaultPostProcessor() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); - given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - ReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); - AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThatExceptionOfType(AuthorizationDeniedException.class) - .isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) - .extracting(Mono::block)) - .withMessage("Access Denied"); - verify(mockReactiveAuthorizationManager).check(any(), any()); - } - - @Test - public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); - given(mockMethodInvocation.proceed()).willReturn(Mono.just("ok")); - ReactiveAuthorizationManager manager = mock(ReactiveAuthorizationManager.class); - given(manager.check(any(), any())) - .willReturn(Mono.error(new MyAuthzDeniedException("denied", new AuthorizationDecision(false)))); - AuthorizationManagerAfterReactiveMethodInterceptor advice = new AuthorizationManagerAfterReactiveMethodInterceptor( - Pointcut.TRUE, manager); - assertThatExceptionOfType(MyAuthzDeniedException.class) - .isThrownBy(() -> ((Mono) advice.invoke(mockMethodInvocation)).block()); - } - - private Object masking(InvocationOnMock invocation) { - MethodInvocationResult result = invocation.getArgument(0); - return result.getResult() + "-masked"; - } - - private Object monoMasking(InvocationOnMock invocation) { - MethodInvocationResult result = invocation.getArgument(0); - return Mono.just(result.getResult() + "-masked"); - } - - interface HandlingReactiveAuthorizationManager - extends ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { - + verify(mockReactiveAuthorizationManager).verify(any(), any()); } class Sample { @@ -282,12 +125,4 @@ Flux flux() { } - static class MyAuthzDeniedException extends AuthorizationDeniedException { - - MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) { - super(msg, authorizationResult); - } - - } - } diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java index 8022609ac7b..210a70e0b4e 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java @@ -25,10 +25,8 @@ import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.AuthenticatedAuthorizationManager; import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; @@ -36,7 +34,6 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; -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.eq; @@ -136,22 +133,4 @@ public void invokeWhenAuthorizationEventPublisherThenUses() throws Throwable { any(AuthorizationDecision.class)); } - @Test - public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() { - AuthorizationManager manager = mock(AuthorizationManager.class); - given(manager.check(any(), any())) - .willThrow(new MyAuthzDeniedException("denied", new AuthorizationDecision(false))); - AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor( - Pointcut.TRUE, manager); - assertThatExceptionOfType(MyAuthzDeniedException.class).isThrownBy(() -> advice.invoke(null)); - } - - static class MyAuthzDeniedException extends AuthorizationDeniedException { - - MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) { - super(msg, authorizationResult); - } - - } - } diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java index 400992eec6e..13f6f405750 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -25,9 +25,6 @@ import org.springframework.aop.Pointcut; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationDeniedException; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ReactiveAuthorizationManager; import static org.assertj.core.api.Assertions.assertThat; @@ -70,15 +67,14 @@ public void invokeMonoWhenMockReactiveAuthorizationManagerThenVerify() throws Th given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))) - .willReturn(Mono.just(new AuthorizationDecision(true))); + given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) .extracting(Mono::block) .isEqualTo("john"); - verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation)); + verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation)); } @Test @@ -88,8 +84,7 @@ public void invokeFluxWhenMockReactiveAuthorizationManagerThenVerify() throws Th given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))) - .willReturn(Mono.just(new AuthorizationDecision((true)))); + given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -97,7 +92,7 @@ public void invokeFluxWhenMockReactiveAuthorizationManagerThenVerify() throws Th .extracting(Flux::collectList) .extracting(Mono::block, InstanceOfAssertFactories.list(String.class)) .containsExactly("john", "bob"); - verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation)); + verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation)); } @Test @@ -107,8 +102,8 @@ public void invokeWhenMockReactiveAuthorizationManagerDeniedThenAccessDeniedExce given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))) - .willReturn(Mono.just(new AuthorizationDecision(false))); + given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation))) + .willReturn(Mono.error(new AccessDeniedException("Access Denied"))); AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -116,119 +111,7 @@ public void invokeWhenMockReactiveAuthorizationManagerDeniedThenAccessDeniedExce .isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) .extracting(Mono::block)) .withMessage("Access Denied"); - verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation)); - } - - @Test - public void invokeMonoWhenDeniedAndPostProcessorThenInvokePostProcessor() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); - given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class))) - .willReturn("***"); - AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) - .extracting(Mono::block) - .isEqualTo("***"); - verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation)); - } - - @Test - public void invokeMonoWhenDeniedAndMonoPostProcessorThenInvokePostProcessor() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); - given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class))) - .willReturn(Mono.just("***")); - AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) - .extracting(Mono::block) - .isEqualTo("***"); - verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation)); - } - - @Test - public void invokeFluxWhenDeniedAndPostProcessorThenInvokePostProcessor() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); - given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class))) - .willReturn(Mono.just("***")); - AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class)) - .extracting(Flux::collectList) - .extracting(Mono::block, InstanceOfAssertFactories.list(String.class)) - .containsExactly("***"); - verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation)); - } - - @Test - public void invokeMonoWhenEmptyDecisionThenInvokeDefaultPostProcessor() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); - given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - ReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThatExceptionOfType(AuthorizationDeniedException.class) - .isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class)) - .extracting(Mono::block)) - .withMessage("Access Denied"); - verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation)); - } - - @Test - public void invokeFluxWhenEmptyDecisionThenInvokeDefaultPostProcessor() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); - given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); - ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - ReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( - Pointcut.TRUE, mockReactiveAuthorizationManager); - Object result = interceptor.invoke(mockMethodInvocation); - assertThatExceptionOfType(AuthorizationDeniedException.class) - .isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class)) - .extracting(Flux::blockFirst)) - .withMessage("Access Denied"); - verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation)); - } - - @Test - public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() throws Throwable { - MethodInvocation mockMethodInvocation = spy( - new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); - ReactiveAuthorizationManager manager = mock(ReactiveAuthorizationManager.class); - given(manager.check(any(), any())) - .willThrow(new MyAuthzDeniedException("denied", new AuthorizationDecision(false))); - AuthorizationManagerBeforeReactiveMethodInterceptor advice = new AuthorizationManagerBeforeReactiveMethodInterceptor( - Pointcut.TRUE, manager); - assertThatExceptionOfType(MyAuthzDeniedException.class) - .isThrownBy(() -> ((Mono) advice.invoke(mockMethodInvocation)).block()); - } - - interface HandlingReactiveAuthorizationManager - extends ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { - + verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation)); } class Sample { @@ -243,12 +126,4 @@ Flux flux() { } - static class MyAuthzDeniedException extends AuthorizationDeniedException { - - MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) { - super(msg, authorizationResult); - } - - } - } diff --git a/core/src/test/java/org/springframework/security/authorization/method/ExpressionUtilsTests.java b/core/src/test/java/org/springframework/security/authorization/method/ExpressionUtilsTests.java deleted file mode 100644 index 7dba1e1a419..00000000000 --- a/core/src/test/java/org/springframework/security/authorization/method/ExpressionUtilsTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import org.junit.jupiter.api.Test; - -import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.ExpressionAuthorizationDecision; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ExpressionUtilsTests { - - private final Object details = new Object(); - - @Test - public void evaluateWhenAuthorizationDecisionThenReturns() { - SpelExpressionParser parser = new SpelExpressionParser(); - Expression expression = parser.parseExpression("#root.returnDecision()"); - StandardEvaluationContext context = new StandardEvaluationContext(this); - assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(AuthorizationDecisionDetails.class) - .extracting("details") - .isEqualTo(this.details); - } - - @Test - public void evaluateWhenBooleanThenReturnsExpressionAuthorizationDecision() { - SpelExpressionParser parser = new SpelExpressionParser(); - Expression expression = parser.parseExpression("#root.returnResult()"); - StandardEvaluationContext context = new StandardEvaluationContext(this); - assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(ExpressionAuthorizationDecision.class); - } - - public AuthorizationDecision returnDecision() { - return new AuthorizationDecisionDetails(false, this.details); - } - - public boolean returnResult() { - return false; - } - - static final class AuthorizationDecisionDetails extends AuthorizationDecision { - - final Object details; - - AuthorizationDecisionDetails(boolean granted, Object details) { - super(granted); - this.details = details; - } - - } - -} diff --git a/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java index 59187469232..5a35b884895 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -225,56 +225,6 @@ public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationE .isThrownBy(() -> manager.check(authentication, methodInvocation)); } - @Test - public void checkRequiresUserWhenMethodsFromInheritThenApplies() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new RolesAllowedClass(), - RolesAllowedClass.class, "securedUser"); - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - public void checkPermitAllWhenMethodsFromInheritThenApplies() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new PermitAllClass(), PermitAllClass.class, - "securedUser"); - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - public void checkDenyAllWhenMethodsFromInheritThenApplies() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new DenyAllClass(), DenyAllClass.class, - "securedUser"); - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); - assertThat(decision.isGranted()).isFalse(); - } - - @RolesAllowed("USER") - public static class RolesAllowedClass extends ParentClass { - - } - - @PermitAll - public static class PermitAllClass extends ParentClass { - - } - - @DenyAll - public static class DenyAllClass extends ParentClass { - - } - - public static class ParentClass { - - public void securedUser() { - - } - - } - public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { public void doSomething() { diff --git a/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManagerTests.java index 8d0cbbf61aa..37383b40d57 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -167,29 +167,6 @@ public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationE .isThrownBy(() -> manager.check(authentication, result)); } - @Test - public void checkRequiresUserWhenMethodsFromInheritThenApplies() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new PostAuthorizeClass(), - PostAuthorizeClass.class, "securedUser"); - MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null); - PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result); - assertThat(decision.isGranted()).isTrue(); - } - - @PostAuthorize("hasRole('USER')") - public static class PostAuthorizeClass extends ParentClass { - - } - - public static class ParentClass { - - public void securedUser() { - - } - - } - public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { public void doSomething() { diff --git a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java index 48eec006186..00f2aed42dd 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -170,34 +170,6 @@ public Object proceed() { SecurityContextHolder.setContextHolderStrategy(saved); } - @Test - public void checkPostFilterWhenMethodsFromInheritThenApplies() throws Throwable { - String[] array = { "john", "bob" }; - MockMethodInvocation methodInvocation = new MockMethodInvocation(new PostFilterClass(), PostFilterClass.class, - "inheritMethod", new Class[] { String[].class }, new Object[] { array }) { - @Override - public Object proceed() { - return array; - } - }; - PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor(); - Object result = advice.invoke(methodInvocation); - assertThat(result).asInstanceOf(InstanceOfAssertFactories.array(String[].class)).containsOnly("john"); - } - - @PostFilter("filterObject == 'john'") - public static class PostFilterClass extends ParentClass { - - } - - public static class ParentClass { - - public String[] inheritMethod(String[] array) { - return array; - } - - } - @PostFilter("filterObject == 'john'") public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java index 6b8153ba618..cb43868dbf0 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -147,28 +147,6 @@ public void checkTargetClassAwareWhenInterfaceLevelAnnotationsThenApplies() thro assertThat(decision.isGranted()).isTrue(); } - @Test - public void checkRequiresUserWhenMethodsFromInheritThenApplies() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new PreAuthorizeClass(), - PreAuthorizeClass.class, "securedUser"); - PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); - assertThat(decision.isGranted()).isTrue(); - } - - @PreAuthorize("hasRole('USER')") - public static class PreAuthorizeClass extends ParentClass { - - } - - public static class ParentClass { - - public void securedUser() { - - } - - } - public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { public void doSomething() { diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java index 30d40a369fe..4f1d56fb146 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -224,32 +224,6 @@ public void preFilterWhenStaticSecurityContextHolderStrategyAfterConstructorThen SecurityContextHolder.setContextHolderStrategy(saved); } - @Test - public void checkPreFilterWhenMethodsFromInheritThenApplies() throws Throwable { - List list = new ArrayList<>(); - list.add("john"); - list.add("bob"); - MockMethodInvocation invocation = new MockMethodInvocation(new PreFilterClass(), PreFilterClass.class, - "inheritMethod", new Class[] { List.class }, new Object[] { list }); - PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); - advice.invoke(invocation); - assertThat(list).hasSize(1); - assertThat(list.get(0)).isEqualTo("john"); - } - - @PreFilter("filterObject == 'john'") - public static class PreFilterClass extends ParentClass { - - } - - public static class ParentClass { - - public void inheritMethod(List list) { - - } - - } - @PreFilter("filterObject == 'john'") public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { diff --git a/core/src/test/java/org/springframework/security/authorization/method/SecuredAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/SecuredAuthorizationManagerTests.java index 5567ad7ae64..5d8651df9e8 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/SecuredAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/SecuredAuthorizationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -167,28 +167,6 @@ public void checkTargetClassAwareWhenInterfaceLevelAnnotationsThenApplies() thro assertThat(decision.isGranted()).isTrue(); } - @Test - public void checkRequiresUserWhenMethodsFromInheritThenApplies() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new SecuredSonClass(), SecuredSonClass.class, - "securedUser"); - SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); - assertThat(decision.isGranted()).isTrue(); - } - - @Secured("ROLE_USER") - public static class SecuredSonClass extends ParentClass { - - } - - public static class ParentClass { - - public void securedUser() { - - } - - } - public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { public void doSomething() { diff --git a/core/src/test/java/org/springframework/security/core/SpringSecurityCoreVersionTests.java b/core/src/test/java/org/springframework/security/core/SpringSecurityCoreVersionTests.java index ff9cec2ed69..a9f9bfdbd36 100644 --- a/core/src/test/java/org/springframework/security/core/SpringSecurityCoreVersionTests.java +++ b/core/src/test/java/org/springframework/security/core/SpringSecurityCoreVersionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Answers; @@ -80,7 +79,6 @@ public void springVersionIsUpToDate() { } @Test - @Disabled("Since 6.3. See gh-3737") public void serialVersionMajorAndMinorVersionMatchBuildVersion() { String version = System.getProperty("springSecurityVersion"); // Strip patch version diff --git a/crypto/spring-security-crypto.gradle b/crypto/spring-security-crypto.gradle index 55aa092b801..b45a20b0d87 100644 --- a/crypto/spring-security-crypto.gradle +++ b/crypto/spring-security-crypto.gradle @@ -3,9 +3,8 @@ apply plugin: 'io.spring.convention.spring-module' dependencies { management platform(project(":spring-security-dependencies")) optional 'org.springframework:spring-jcl' - optional 'org.springframework:spring-core' - optional 'org.bouncycastle:bcpkix-jdk18on' - + optional 'org.bouncycastle:bcpkix-jdk15on' + testImplementation "org.assertj:assertj-core" testImplementation "org.junit.jupiter:junit-jupiter-api" testImplementation "org.junit.jupiter:junit-jupiter-params" diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java deleted file mode 100644 index 9c226042f2b..00000000000 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2013-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.crypto.encrypt; - -import java.io.InputStream; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.interfaces.RSAPrivateCrtKey; -import java.security.spec.RSAPublicKeySpec; - -import org.springframework.core.io.Resource; -import org.springframework.util.StringUtils; - -/** - * @author Dave Syer - * @author Tim Ysewyn - * @since 6.3 - */ -public class KeyStoreKeyFactory { - - private final Resource resource; - - private final char[] password; - - private KeyStore store; - - private final Object lock = new Object(); - - private final String type; - - public KeyStoreKeyFactory(Resource resource, char[] password) { - this(resource, password, type(resource)); - } - - private static String type(Resource resource) { - String ext = StringUtils.getFilenameExtension(resource.getFilename()); - return (ext != null) ? ext : "jks"; - } - - public KeyStoreKeyFactory(Resource resource, char[] password, String type) { - this.resource = resource; - this.password = password; - this.type = type; - } - - public KeyPair getKeyPair(String alias) { - return getKeyPair(alias, this.password); - } - - public KeyPair getKeyPair(String alias, char[] password) { - try { - synchronized (this.lock) { - if (this.store == null) { - synchronized (this.lock) { - this.store = KeyStore.getInstance(this.type); - try (InputStream stream = this.resource.getInputStream()) { - this.store.load(stream, this.password); - } - } - } - } - RSAPrivateCrtKey key = (RSAPrivateCrtKey) this.store.getKey(alias, password); - Certificate certificate = this.store.getCertificate(alias); - PublicKey publicKey = null; - if (certificate != null) { - publicKey = certificate.getPublicKey(); - } - else if (key != null) { - RSAPublicKeySpec spec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); - publicKey = KeyFactory.getInstance("RSA").generatePublic(spec); - } - return new KeyPair(publicKey, key); - } - catch (Exception ex) { - throw new IllegalStateException("Cannot load keys from store: " + this.resource, ex); - } - } - -} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaAlgorithm.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaAlgorithm.java deleted file mode 100644 index c22a173df46..00000000000 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaAlgorithm.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2013-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.crypto.encrypt; - -/** - * @author Dave Syer - * @since 6.3 - */ -public enum RsaAlgorithm { - - DEFAULT("RSA", 117), OAEP("RSA/ECB/OAEPPadding", 86); - - private final String name; - - private final int maxLength; - - RsaAlgorithm(String name, int maxLength) { - this.name = name; - this.maxLength = maxLength; - } - - public String getJceName() { - return this.name; - } - - public int getMaxLength() { - return this.maxLength; - } - -} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java deleted file mode 100644 index fd3e058a119..00000000000 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright 2013-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.crypto.encrypt; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.StringWriter; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; -import java.security.spec.RSAPrivateCrtKeySpec; -import java.security.spec.RSAPublicKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Arrays; -import java.util.Base64; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.bouncycastle.asn1.ASN1Sequence; - -/** - * Reads RSA key pairs using BC provider classes but without the need to specify a crypto - * provider or have BC added as one. - * - * @author Luke Taylor - * @author Dave Syer - */ -final class RsaKeyHelper { - - private static final Charset UTF8 = StandardCharsets.UTF_8; - - private static final String BEGIN = "-----BEGIN"; - - private static final Pattern PEM_DATA = Pattern.compile(".*-----BEGIN (.*)-----(.*)-----END (.*)-----", - Pattern.DOTALL); - - private static final byte[] PREFIX = new byte[] { 0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a' }; - - private RsaKeyHelper() { - } - - static KeyPair parseKeyPair(String pemData) { - Matcher m = PEM_DATA.matcher(pemData.replaceAll("\n *", "").trim()); - - if (!m.matches()) { - try { - RSAPublicKey publicValue = extractPublicKey(pemData); - if (publicValue != null) { - return new KeyPair(publicValue, null); - } - } - catch (Exception ex) { - // Ignore - } - throw new IllegalArgumentException("String is not PEM encoded data, nor a public key encoded for ssh"); - } - - String type = m.group(1); - final byte[] content = base64Decode(m.group(2)); - - PublicKey publicKey; - PrivateKey privateKey = null; - - try { - KeyFactory fact = KeyFactory.getInstance("RSA"); - switch (type) { - case "RSA PRIVATE KEY" -> { - ASN1Sequence seq = ASN1Sequence.getInstance(content); - if (seq.size() != 9) { - throw new IllegalArgumentException("Invalid RSA Private Key ASN1 sequence."); - } - org.bouncycastle.asn1.pkcs.RSAPrivateKey key = org.bouncycastle.asn1.pkcs.RSAPrivateKey - .getInstance(seq); - RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); - RSAPrivateCrtKeySpec privSpec = new RSAPrivateCrtKeySpec(key.getModulus(), key.getPublicExponent(), - key.getPrivateExponent(), key.getPrime1(), key.getPrime2(), key.getExponent1(), - key.getExponent2(), key.getCoefficient()); - publicKey = fact.generatePublic(pubSpec); - privateKey = fact.generatePrivate(privSpec); - } - case "PUBLIC KEY" -> { - KeySpec keySpec = new X509EncodedKeySpec(content); - publicKey = fact.generatePublic(keySpec); - } - case "RSA PUBLIC KEY" -> { - ASN1Sequence seq = ASN1Sequence.getInstance(content); - org.bouncycastle.asn1.pkcs.RSAPublicKey key = org.bouncycastle.asn1.pkcs.RSAPublicKey - .getInstance(seq); - RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); - publicKey = fact.generatePublic(pubSpec); - } - default -> throw new IllegalArgumentException(type + " is not a supported format"); - } - - return new KeyPair(publicKey, privateKey); - } - catch (InvalidKeySpecException ex) { - throw new RuntimeException(ex); - } - catch (NoSuchAlgorithmException ex) { - throw new IllegalStateException(ex); - } - } - - private static byte[] base64Decode(String string) { - try { - ByteBuffer bytes = UTF8.newEncoder().encode(CharBuffer.wrap(string)); - byte[] bytesCopy = new byte[bytes.limit()]; - System.arraycopy(bytes.array(), 0, bytesCopy, 0, bytes.limit()); - return Base64.getDecoder().decode(bytesCopy); - } - catch (CharacterCodingException ex) { - throw new RuntimeException(ex); - } - } - - static String base64Encode(byte[] bytes) { - try { - return UTF8.newDecoder().decode(ByteBuffer.wrap(Base64.getEncoder().encode(bytes))).toString(); - } - catch (CharacterCodingException ex) { - throw new RuntimeException(ex); - } - } - - static KeyPair generateKeyPair() { - try { - final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); - keyGen.initialize(1024); - return keyGen.generateKeyPair(); - } - catch (NoSuchAlgorithmException ex) { - throw new IllegalStateException(ex); - } - - } - - private static final Pattern SSH_PUB_KEY = Pattern.compile("ssh-(rsa|dsa) ([A-Za-z0-9/+]+=*) (.*)"); - - private static RSAPublicKey extractPublicKey(String key) { - - Matcher m = SSH_PUB_KEY.matcher(key); - - if (m.matches()) { - String alg = m.group(1); - String encKey = m.group(2); - // String id = m.group(3); - - if (!"rsa".equalsIgnoreCase(alg)) { - throw new IllegalArgumentException("Only RSA is currently supported, but algorithm was " + alg); - } - - return parseSSHPublicKey(encKey); - } - else if (!key.startsWith(BEGIN)) { - // Assume it's the plain Base64 encoded ssh key without the - // "ssh-rsa" at the start - return parseSSHPublicKey(key); - } - - return null; - } - - static RSAPublicKey parsePublicKey(String key) { - - RSAPublicKey publicKey = extractPublicKey(key); - - if (publicKey != null) { - return publicKey; - } - - KeyPair kp = parseKeyPair(key); - - if (kp.getPublic() == null) { - throw new IllegalArgumentException("Key data does not contain a public key"); - } - - return (RSAPublicKey) kp.getPublic(); - - } - - static String encodePublicKey(RSAPublicKey key, String id) { - StringWriter output = new StringWriter(); - output.append("ssh-rsa "); - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - try { - stream.write(PREFIX); - writeBigInteger(stream, key.getPublicExponent()); - writeBigInteger(stream, key.getModulus()); - } - catch (IOException ex) { - throw new IllegalStateException("Cannot encode key", ex); - } - output.append(base64Encode(stream.toByteArray())); - output.append(" " + id); - return output.toString(); - } - - private static RSAPublicKey parseSSHPublicKey(String encKey) { - ByteArrayInputStream in = new ByteArrayInputStream(base64Decode(encKey)); - - byte[] prefix = new byte[11]; - - try { - if (in.read(prefix) != 11 || !Arrays.equals(PREFIX, prefix)) { - throw new IllegalArgumentException("SSH key prefix not found"); - } - - BigInteger e = new BigInteger(readBigInteger(in)); - BigInteger n = new BigInteger(readBigInteger(in)); - - return createPublicKey(n, e); - } - catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - static RSAPublicKey createPublicKey(BigInteger n, BigInteger e) { - try { - return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(n, e)); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private static void writeBigInteger(ByteArrayOutputStream stream, BigInteger num) throws IOException { - int length = num.toByteArray().length; - byte[] data = new byte[4]; - data[0] = (byte) ((length >> 24) & 0xFF); - data[1] = (byte) ((length >> 16) & 0xFF); - data[2] = (byte) ((length >> 8) & 0xFF); - data[3] = (byte) (length & 0xFF); - stream.write(data); - stream.write(num.toByteArray()); - } - - private static byte[] readBigInteger(ByteArrayInputStream in) throws IOException { - byte[] b = new byte[4]; - - if (in.read(b) != 4) { - throw new IOException("Expected length data as 4 bytes"); - } - - int l = ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) | ((b[2] & 0xFF) << 8) | (b[3] & 0xFF); - - b = new byte[l]; - - if (in.read(b) != l) { - throw new IOException("Expected " + l + " key bytes"); - } - - return b; - } - -} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHolder.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHolder.java deleted file mode 100644 index 49ae22e62f8..00000000000 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHolder.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2013-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.crypto.encrypt; - -/** - * @author Dave Syer - * @since 6.3 - */ -public interface RsaKeyHolder { - - String getPublicKey(); - -} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java deleted file mode 100644 index 655ea45b08e..00000000000 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2013-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.crypto.encrypt; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.Charset; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.interfaces.RSAKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.util.Base64; - -import javax.crypto.Cipher; - -/** - * @author Dave Syer - * @since 6.3 - */ -public class RsaRawEncryptor implements BytesEncryptor, TextEncryptor, RsaKeyHolder { - - private static final String DEFAULT_ENCODING = "UTF-8"; - - private RsaAlgorithm algorithm = RsaAlgorithm.DEFAULT; - - private Charset charset; - - private RSAPublicKey publicKey; - - private RSAPrivateKey privateKey; - - private Charset defaultCharset; - - public RsaRawEncryptor(RsaAlgorithm algorithm) { - this(RsaKeyHelper.generateKeyPair(), algorithm); - } - - public RsaRawEncryptor() { - this(RsaKeyHelper.generateKeyPair()); - } - - public RsaRawEncryptor(KeyPair keyPair, RsaAlgorithm algorithm) { - this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm); - } - - public RsaRawEncryptor(KeyPair keyPair) { - this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate()); - } - - public RsaRawEncryptor(String pemData) { - this(RsaKeyHelper.parseKeyPair(pemData)); - } - - public RsaRawEncryptor(PublicKey publicKey) { - this(DEFAULT_ENCODING, publicKey, null); - } - - public RsaRawEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey) { - this(encoding, publicKey, privateKey, RsaAlgorithm.DEFAULT); - } - - public RsaRawEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm) { - this.charset = Charset.forName(encoding); - this.publicKey = (RSAPublicKey) publicKey; - this.privateKey = (RSAPrivateKey) privateKey; - this.defaultCharset = Charset.forName(DEFAULT_ENCODING); - this.algorithm = algorithm; - } - - @Override - public String getPublicKey() { - return RsaKeyHelper.encodePublicKey(this.publicKey, "application"); - } - - @Override - public String encrypt(String text) { - return new String(Base64.getEncoder().encode(encrypt(text.getBytes(this.charset))), this.defaultCharset); - } - - @Override - public String decrypt(String encryptedText) { - if (this.privateKey == null) { - throw new IllegalStateException("Private key must be provided for decryption"); - } - return new String(decrypt(Base64.getDecoder().decode(encryptedText.getBytes(this.defaultCharset))), - this.charset); - } - - @Override - public byte[] encrypt(byte[] byteArray) { - return encrypt(byteArray, this.publicKey, this.algorithm); - } - - @Override - public byte[] decrypt(byte[] encryptedByteArray) { - return decrypt(encryptedByteArray, this.privateKey, this.algorithm); - } - - private static byte[] encrypt(byte[] text, PublicKey key, RsaAlgorithm alg) { - ByteArrayOutputStream output = new ByteArrayOutputStream(text.length); - try { - final Cipher cipher = Cipher.getInstance(alg.getJceName()); - int limit = Math.min(text.length, alg.getMaxLength()); - int pos = 0; - while (pos < text.length) { - cipher.init(Cipher.ENCRYPT_MODE, key); - cipher.update(text, pos, limit); - pos += limit; - limit = Math.min(text.length - pos, alg.getMaxLength()); - byte[] buffer = cipher.doFinal(); - output.write(buffer, 0, buffer.length); - } - return output.toByteArray(); - } - catch (RuntimeException ex) { - throw ex; - } - catch (Exception ex) { - throw new IllegalStateException("Cannot encrypt", ex); - } - } - - private static byte[] decrypt(byte[] text, RSAPrivateKey key, RsaAlgorithm alg) { - ByteArrayOutputStream output = new ByteArrayOutputStream(text.length); - try { - final Cipher cipher = Cipher.getInstance(alg.getJceName()); - int maxLength = getByteLength(key); - int pos = 0; - while (pos < text.length) { - int limit = Math.min(text.length - pos, maxLength); - cipher.init(Cipher.DECRYPT_MODE, key); - cipher.update(text, pos, limit); - pos += limit; - byte[] buffer = cipher.doFinal(); - output.write(buffer, 0, buffer.length); - } - return output.toByteArray(); - } - catch (RuntimeException ex) { - throw ex; - } - catch (Exception ex) { - throw new IllegalStateException("Cannot decrypt", ex); - } - } - - // copied from sun.security.rsa.RSACore.getByteLength(java.math.BigInteger) - public static int getByteLength(RSAKey key) { - int n = key.getModulus().bitLength(); - return (n + 7) >> 3; - } - -} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java deleted file mode 100644 index ad8b76d4fb4..00000000000 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2013-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.crypto.encrypt; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.Charset; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.interfaces.RSAPublicKey; -import java.util.Base64; - -import javax.crypto.Cipher; - -import org.springframework.security.crypto.codec.Hex; -import org.springframework.security.crypto.keygen.KeyGenerators; - -/** - * @author Dave Syer - * @since 6.3 - */ -public class RsaSecretEncryptor implements BytesEncryptor, TextEncryptor, RsaKeyHolder { - - private static final String DEFAULT_ENCODING = "UTF-8"; - - // The secret for encryption is random (so dictionary attack is not a danger) - private static final String DEFAULT_SALT = "deadbeef"; - - private final String salt; - - private RsaAlgorithm algorithm = RsaAlgorithm.DEFAULT; - - private final Charset charset; - - private final PublicKey publicKey; - - private final PrivateKey privateKey; - - private final Charset defaultCharset; - - private final boolean gcm; - - public RsaSecretEncryptor(RsaAlgorithm algorithm, String salt, boolean gcm) { - this(RsaKeyHelper.generateKeyPair(), algorithm, salt, gcm); - } - - public RsaSecretEncryptor(RsaAlgorithm algorithm, String salt) { - this(RsaKeyHelper.generateKeyPair(), algorithm, salt); - } - - public RsaSecretEncryptor(RsaAlgorithm algorithm, boolean gcm) { - this(RsaKeyHelper.generateKeyPair(), algorithm, DEFAULT_SALT, gcm); - } - - public RsaSecretEncryptor(RsaAlgorithm algorithm) { - this(RsaKeyHelper.generateKeyPair(), algorithm); - } - - public RsaSecretEncryptor() { - this(RsaKeyHelper.generateKeyPair()); - } - - public RsaSecretEncryptor(KeyPair keyPair, RsaAlgorithm algorithm, String salt, boolean gcm) { - this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm, salt, gcm); - } - - public RsaSecretEncryptor(KeyPair keyPair, RsaAlgorithm algorithm, String salt) { - this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm, salt, false); - } - - public RsaSecretEncryptor(KeyPair keyPair, RsaAlgorithm algorithm) { - this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm); - } - - public RsaSecretEncryptor(KeyPair keyPair) { - this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate()); - } - - public RsaSecretEncryptor(String pemData, RsaAlgorithm algorithm, String salt) { - this(RsaKeyHelper.parseKeyPair(pemData), algorithm, salt); - } - - public RsaSecretEncryptor(String pemData, RsaAlgorithm algorithm) { - this(RsaKeyHelper.parseKeyPair(pemData), algorithm); - } - - public RsaSecretEncryptor(String pemData) { - this(RsaKeyHelper.parseKeyPair(pemData)); - } - - public RsaSecretEncryptor(PublicKey publicKey, RsaAlgorithm algorithm, String salt, boolean gcm) { - this(DEFAULT_ENCODING, publicKey, null, algorithm, salt, gcm); - } - - public RsaSecretEncryptor(PublicKey publicKey, RsaAlgorithm algorithm, String salt) { - this(DEFAULT_ENCODING, publicKey, null, algorithm, salt, false); - } - - public RsaSecretEncryptor(PublicKey publicKey, RsaAlgorithm algorithm) { - this(DEFAULT_ENCODING, publicKey, null, algorithm); - } - - public RsaSecretEncryptor(PublicKey publicKey) { - this(DEFAULT_ENCODING, publicKey, null); - } - - public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey) { - this(encoding, publicKey, privateKey, RsaAlgorithm.DEFAULT); - } - - public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm) { - this(encoding, publicKey, privateKey, algorithm, DEFAULT_SALT, false); - } - - public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm, - String salt, boolean gcm) { - this.charset = Charset.forName(encoding); - this.publicKey = publicKey; - this.privateKey = privateKey; - this.defaultCharset = Charset.forName(DEFAULT_ENCODING); - this.algorithm = algorithm; - this.salt = isHex(salt) ? salt : new String(Hex.encode(salt.getBytes(this.defaultCharset))); - this.gcm = gcm; - } - - @Override - public String getPublicKey() { - return RsaKeyHelper.encodePublicKey((RSAPublicKey) this.publicKey, "application"); - } - - @Override - public String encrypt(String text) { - return new String(Base64.getEncoder().encode(encrypt(text.getBytes(this.charset))), this.defaultCharset); - } - - @Override - public String decrypt(String encryptedText) { - if (!canDecrypt()) { - throw new IllegalStateException("Encryptor is not configured for decryption"); - } - return new String(decrypt(Base64.getDecoder().decode(encryptedText.getBytes(this.defaultCharset))), - this.charset); - } - - @Override - public byte[] encrypt(byte[] byteArray) { - return encrypt(byteArray, this.publicKey, this.algorithm, this.salt, this.gcm); - } - - @Override - public byte[] decrypt(byte[] encryptedByteArray) { - if (!canDecrypt()) { - throw new IllegalStateException("Encryptor is not configured for decryption"); - } - return decrypt(encryptedByteArray, this.privateKey, this.algorithm, this.salt, this.gcm); - } - - private static byte[] encrypt(byte[] text, PublicKey key, RsaAlgorithm alg, String salt, boolean gcm) { - byte[] random = KeyGenerators.secureRandom(16).generateKey(); - BytesEncryptor aes = gcm ? Encryptors.stronger(new String(Hex.encode(random)), salt) - : Encryptors.standard(new String(Hex.encode(random)), salt); - try { - final Cipher cipher = Cipher.getInstance(alg.getJceName()); - cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] secret = cipher.doFinal(random); - ByteArrayOutputStream result = new ByteArrayOutputStream(text.length + 20); - writeInt(result, secret.length); - result.write(secret); - result.write(aes.encrypt(text)); - return result.toByteArray(); - } - catch (RuntimeException ex) { - throw ex; - } - catch (Exception ex) { - throw new IllegalStateException("Cannot encrypt", ex); - } - } - - private static void writeInt(ByteArrayOutputStream result, int length) throws IOException { - byte[] data = new byte[2]; - data[0] = (byte) ((length >> 8) & 0xFF); - data[1] = (byte) (length & 0xFF); - result.write(data); - } - - private static int readInt(ByteArrayInputStream result) throws IOException { - byte[] b = new byte[2]; - result.read(b); - return ((b[0] & 0xFF) << 8) | (b[1] & 0xFF); - } - - private static byte[] decrypt(byte[] text, PrivateKey key, RsaAlgorithm alg, String salt, boolean gcm) { - ByteArrayInputStream input = new ByteArrayInputStream(text); - ByteArrayOutputStream output = new ByteArrayOutputStream(text.length); - try { - int length = readInt(input); - byte[] random = new byte[length]; - input.read(random); - final Cipher cipher = Cipher.getInstance(alg.getJceName()); - cipher.init(Cipher.DECRYPT_MODE, key); - String secret = new String(Hex.encode(cipher.doFinal(random))); - byte[] buffer = new byte[text.length - random.length - 2]; - input.read(buffer); - BytesEncryptor aes = gcm ? Encryptors.stronger(secret, salt) : Encryptors.standard(secret, salt); - output.write(aes.decrypt(buffer)); - return output.toByteArray(); - } - catch (RuntimeException ex) { - throw ex; - } - catch (Exception ex) { - throw new IllegalStateException("Cannot decrypt", ex); - } - } - - private static boolean isHex(String input) { - try { - Hex.decode(input); - return true; - } - catch (Exception ex) { - return false; - } - } - - public boolean canDecrypt() { - return this.privateKey != null; - } - -} diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactoryTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactoryTests.java deleted file mode 100644 index 79868264db8..00000000000 --- a/crypto/src/test/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactoryTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2013-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.crypto.encrypt; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; - -import org.springframework.core.io.ClassPathResource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dave Syer - * - */ -@DisabledOnOs(OS.WINDOWS) -public class KeyStoreKeyFactoryTests { - - @Test - public void initializeEncryptorFromKeyStore() { - char[] password = "foobar".toCharArray(); - KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), password); - RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("test")); - assertThat(encryptor.canDecrypt()).as("Should be able to decrypt").isTrue(); - assertThat(encryptor.decrypt(encryptor.encrypt("foo"))).isEqualTo("foo"); - } - - @Test - public void initializeEncryptorFromPkcs12KeyStore() { - char[] password = "letmein".toCharArray(); - KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.pkcs12"), password); - RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("mytestkey")); - assertThat(encryptor.canDecrypt()).as("Should be able to decrypt").isTrue(); - assertThat(encryptor.decrypt(encryptor.encrypt("foo"))).isEqualTo("foo"); - } - - @Test - public void initializeEncryptorFromTrustedCertificateInKeyStore() { - char[] password = "foobar".toCharArray(); - KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), password); - RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("testcertificate")); - assertThat(encryptor.canDecrypt()).as("Should not be able to decrypt").isFalse(); - assertThat(encryptor.encrypt("foo")).isNotEqualTo("foo"); - } - - @Test - public void initializeEncryptorFromTrustedCertificateInPkcs12KeyStore() { - char[] password = "letmein".toCharArray(); - KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.pkcs12"), password); - RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("mytestcertificate")); - assertThat(encryptor.canDecrypt()).as("Should not be able to decrypt").isFalse(); - assertThat(encryptor.encrypt("foo")).isNotEqualTo("foo"); - } - -} diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaKeyHelperTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaKeyHelperTests.java deleted file mode 100644 index 593681fe1ef..00000000000 --- a/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaKeyHelperTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2013-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.crypto.encrypt; - -import java.nio.charset.StandardCharsets; -import java.security.KeyPair; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.util.StreamUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -@DisabledOnOs(OS.WINDOWS) -public class RsaKeyHelperTests { - - @Test - public void parsePrivateKey() throws Exception { - // ssh-keygen -m pem -b 1024 -f src/test/resources/fake.pem - String pem = StreamUtils.copyToString(new ClassPathResource("/fake.pem", getClass()).getInputStream(), - StandardCharsets.UTF_8); - KeyPair result = RsaKeyHelper.parseKeyPair(pem); - assertThat(result.getPrivate().getEncoded().length > 0).isTrue(); - assertThat(result.getPrivate().getAlgorithm()).isEqualTo("RSA"); - } - - @Test - public void parseSpaceyKey() throws Exception { - String pem = StreamUtils.copyToString(new ClassPathResource("/spacey.pem", getClass()).getInputStream(), - StandardCharsets.UTF_8); - KeyPair result = RsaKeyHelper.parseKeyPair(pem); - assertThat(result.getPrivate().getEncoded().length > 0).isTrue(); - assertThat(result.getPrivate().getAlgorithm()).isEqualTo("RSA"); - } - - @Test - public void parseBadKey() throws Exception { - // ssh-keygen -m pem -b 1024 -f src/test/resources/fake.pem - String pem = StreamUtils.copyToString(new ClassPathResource("/bad.pem", getClass()).getInputStream(), - StandardCharsets.UTF_8); - try { - RsaKeyHelper.parseKeyPair(pem); - throw new IllegalStateException("Expected IllegalArgumentException"); - } - catch (IllegalArgumentException ex) { - assertThat(ex.getMessage().contains("PEM")).isTrue(); - } - } - -} diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaRawEncryptorTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaRawEncryptorTests.java deleted file mode 100644 index edcc4ee420a..00000000000 --- a/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaRawEncryptorTests.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2013-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.crypto.encrypt; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dave Syer - * - */ -public class RsaRawEncryptorTests { - - private RsaRawEncryptor encryptor = new RsaRawEncryptor(); - - @BeforeEach - public void init() { - LONG_STRING = SHORT_STRING + SHORT_STRING + SHORT_STRING + SHORT_STRING; - for (int i = 0; i < 4; i++) { - LONG_STRING = LONG_STRING + LONG_STRING; - } - } - - @Test - public void roundTrip() { - assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - @Test - public void roundTripOeap() { - this.encryptor = new RsaRawEncryptor(RsaAlgorithm.OAEP); - assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - @Test - public void roundTripLongString() { - assertThat(this.encryptor.decrypt(this.encryptor.encrypt(LONG_STRING))).isEqualTo(LONG_STRING); - } - - @Test - public void roundTripLongStringOeap() { - this.encryptor = new RsaRawEncryptor(RsaAlgorithm.OAEP); - assertThat(this.encryptor.decrypt(this.encryptor.encrypt(LONG_STRING))).isEqualTo(LONG_STRING); - } - - @Test - public void roundTrip2048Key() { - String pemData = "-----BEGIN RSA PRIVATE KEY-----" - + "MIIEpQIBAAKCAQEA5KHEkCudAHCKIUHKyW6Z8dMyQsKrLbpDe0wDzx9MBARcOoS9" - + "ZUjzXwK6p/0RM6aCp+b9kkr37QKQ9K/Am13sr0z8Mkn1Q2cvXiL5gbnY1nYGk8/m" - + "CBX3QEhH2UII4yJsDVx1xmcSorZaWmeNKor7Zl3SZaQpWTvlkMgQKwY8DZL6PPxt" - + "JRPeKmuUY6B59u5okh1G6Y9OnT2dVxAkqT8WgLHu6StxBmueJ272x2sUWUzoDhnP" - + "7JRqa7h7t6fml3o3Op1iCywCOFzCIcK6G/oG/WZ7tbBYkwQdDjn/9VMdKkkPufwq" - + "zt4S75NJygXDwDnNPiTVoaOwrRrL8ahgw6bFCQIDAQABAoIBAECIMHUI+l2fZj2Q" - + "1m4Ym7cYB320eKCFjHqGsCSMDuarXGTgBp1KA/dzS8ASvAI6I3LEzhm2s1fge420" - + "9cZksmOgdSa0nVeTDlmhwY8OJ9gQpDagXas2l/066Zy2+M8zbhAvYsbHXQk0MziF" - + "NeEmLWNtY+9wcINRVrCQ549dSSIDK6UX21oU6d1mrlnF5/bbbdDIM3dKok355jwx" - + "0HFY0tJIs1zArsBVoz3Ccu1MQEfnxEFM1LLPi5rE6cuHIOBinbD1OQ2R/HM2aukG" - + "Rk2m6F3wAieJ7zpt5yaHuuIedn8p8m2NVulXAjgkY2oQl3GGiDH/H7eZlrvQRg6E" - + "D8Bq+ykCgYEA+AfPXVeeVg3Qu0KsNrACek/o92BMY9g3GyPVGULGvq9seoNB86hj" - + "nXasqngBfTlOfJFiahoEzRBB9hIyo1zMw4x99pR8nGxhR3aU+v8EGftMABGHWsB9" - + "Jxj4YQH4fhi57iBa72QmNPbu/1o7y3SEe68E5PJ8KY3jc4xos8Vl658CgYEA6/pk" - + "t6WZII+9lpxQfePQDIlBWAphiQceh995bGXfDmX3vOVmPozix9/fUtF1TeKS/ypw" - + "u++Qmvj5oMsBVrjCyoOYfHKE2vGrLoEzkX/sPO65IsV00geZZoyCEKEE3USJfY46" - + "u0hs61oP8HJyLhLiYiGcFTzZ4nEvvEbiM4E/DlcCgYEA6S0OecZhiK08SpAHrvIR" - + "okN11PqnVkZyqAUr1a+9gI8TAKpdWmA4JlTnRuvDGqLBcsKLLwx+7voVyOyaxpH7" - + "vutZkHNQIw6Q9co5jS4qAPMLJBVWlq7X+eWzvB9KKeG9Cm1IkD4q3Sg4z79Y75D+" - + "6/hCNarxp29JIdwior81bikCgYEApp1P+b7pxGzZPvs1df2hCwjqY0BJJ5goPWVT" - + "dW7kNGVYqz4JmAafpOJz6yTLP2fHxHRxzrBSmKlMj/RmCJZBqv2Jb+zn0zMpW5eM" - + "EqKQ6WDgxSVH23fUHuz8dMNMDPL0ZPtEirGTfgVEFdCov9FDmGgErZYefVzPiI8/" - + "7X/HRtcCgYEApQ2YS+0DLPqaM0cC6/6hDr/jmHLFhHaV6DZR7M9HHDnMN2uMlOEa" - + "RYvXRMBjyQ7LQkwOj6K5k8MVrsDDM5dbekTBgcJMHfM9uViDkB0VPYULORmDJ20N" - + "MLowIAiSon2B2/isatY80YtFq+bRyvPOzjGvinHN3MU1GH/gFuS0fiw=" + "-----END RSA PRIVATE KEY-----"; - RsaRawEncryptor encryptor_2048 = new RsaRawEncryptor(pemData); - assertThat(encryptor_2048.decrypt(encryptor_2048.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - @Test - public void roundTrip4096Key() { - String pemData = "-----BEGIN RSA PRIVATE KEY-----" - + "MIIJKAIBAAKCAgEAw/OIcO1pv8t/lhXwzc+CqCqAE8+2+BTWd6fHy8P2oGKZK0s3" - + "jxPWdZEbp1soGZobCIjEIuYuuPeinrTFOxtnf/JVfmzGnixRjWzQK0UiM/4z8GW6" - + "7+dzB0+QZlU+PGCL6xra4d3+5EsPQwTDjPJ4OhcA66hWACd3UJpvE2C14YdFkCP/" - + "CUxubz1l+8rFwEtMcw2bVUL/Mt+Sx1CHPFer17VK/sT4urwNG7y9R8WWvNQXgEwg" - + "0im+iJ0zf1u0SdUVj+Q1LwgNRoIx4vec2xAJ6xdqSx3Y3g2twWqUXUBb5K09ajIW" - + "Vuko5kWJVyx1x8LazU+0wQRLVJRYAiUOPLg7PdPAJWaAWmagnkAvl5bqCKi6sIc8" - + "+vKyrPx4VJH5KLsHx8020Wgch/LfHl/vvoHE7Oa81hnyMVsApvNCJdFbiMJ6r2z/" - + "eHqzjY8lzBQHNxh1XJys5teTJsi6N06gCc+OQRyw1FQ8KLgFlLPHNamfMnP5Ju0d" - + "Jv8GzQiMFjudjEYhkh2GPmRus1VYWDwDWhXwp28koWAanfih+Ujc2ZqNUS23hGWz" - + "KbCxRaAwSLqn3vkoYBeDyWWs1r0HnB6gACFaZIk38aiGyg7GjF0286Aq7USqNwKu" - + "Izm4kzIPFrHIbywKq7804J7wXUlaAgf0pNSndMD5OnwudzD+JHLTuOGFNdUCAwEA" - + "AQKCAgBYh2mIY6rYTS9adpUx1uPX6EOvL7QhhwCSVMoupF2Dfqhm5/e0+6hzu1h8" - + "FvIaBwbZpzi977MCPFdLTq6hErODGdBIawqdIbbCp3uxYO2gAeQjY0K+6pmMnwTF" - + "RxP0IUZ1tM9ZJnvnVoYRqFBVGKL607PFxGr+bNY6I1u1rIbf2sax5aFu6Qon1dyC" - + "ks0fIKXsgSRBtCAqMtpUlGxU9eMcdLrqOcGKVDWz52S4zWtZ6pSnkT1u1g9QF33R" - + "t3PPu6afOOJSWlftGBtDyM0kJ63jedO7FkQJprJu5SEctFwQB7jshq6TG4ov5xCy" - + "wtJ/quhBxBYM8ky6bL8KUQWKp02Tyfq0Fo+iwuLxM4N6LxVPFZ6R6jwvazm+ka4S" - + "sZAW/hnH3FdJEAyFcxzhelLdLUrjwrsWjmJBk0pMP5cEleYR8PQh2sHM8ZOX1T5f" - + "4zfyR66+tl1O81T7anbma8l1Wm/QSNZz+8QAM1iNuV+uLsWvmxLAc7NRgjDmiAMn" - + "8VhfUtl0ooOZYkDexqSNaWvIQG+S8Pl28gNxVXkXrXqBGPJn2ptROEJ1/AN1h4cv" - + "2CktVylRFpEI/hxXvKMaAu/tXtvoakvaTA8msl8Otrldsy3EGhgHrDTYIJUg/rRT" - + "TlbRkN/ycaOhA0d4HAewOGul3ss+EtBz+SQBzaWm2Inr8XOJoQKCAQEA4LwW7eGm" - + "MOYspFUbn2tMlnJAng9HKK42o2m6ShYAaQAoLX7LIkQYVS++9CiGCPpoSlwIJWE3" - + "N/qGx0i7REDm+wNu0/4acaMFI+qYtvjKiWwtMOBH3bw1C4/Isc60tFPkI7FEFCiF" - + "SiW3c+Z8B0/IRMb/YF5tZeuWUlAl7PQJ1rMcPUE4O4LXM4BG29hghVGGnp39YsOY" - + "b/6oBApTgdxCaSZhmhDwTMu97n75CK0xzA2vDtHn2Gu3zf4j6bsNot6/7wRtQBMg" - + "1e3kXuwGUZ08QZ7OqATUIZdCeK1PfxypontVh+0LeNjiDU8pW3Q8IMlDT96Fd5U+" - + "BgtjfHmwHXeBmQKCAQEA3zZS619O/IUoWN3rWT4hUSJE3S+FXXcaBaJ7H6r897cl" - + "ju+HSS2CLp/C9ftcQ9ef+pG2arLRZpONd5KhfRyjo0pNp3SwxklnIhNS9abBBCnN" - + "ojeYcVHOcSfmWGlUCQAvv5LeBPSS02pbCE5t/qadglvgKhHqSb2u+FgkdKrV0Mme" - + "sbVy+tyd4F1oBIS0wg1p3mHKvKfb4MEnUDvIvG8rCBUMvAWQmTiuyqFUiuqSwEMy" - + "LANFFV/ZoJ5194ruTXdelcoZjXhd128JJFNp6Jh4eg5OWoBS7e08QHbvUYBppDYO" - + "Iz0N1TipVK9uCqHHtbwIqqxyPVev3QJUYkpl5/tznQKCAQB9izV38F2J5Zu8tbq3" - + "pRZk2TCV280RwbjOMysZZg8WmTrYp4NNAiNhu0l+VgEClPibyavXTeauA+s0+sF6" - + "kJM4WKOaE9Kr9rjRZqWnWXazrFXWfwRGr3QmoE0qX2H9dvv0oHt6k2RalpVUTsas" - + "wvoKyewx5q5QiHoyQ4ncRDwWz3oQEhYa0K3tnFR5TfglofSFOZcqjD/lGKq9jxM1" - + "cVk8Km/NxHapQAw7Zn0yRqaR6ncH3WUaNpq4nadsU817Vdp86MkrSURHnhy8lje1" - + "chQOSGwD2qaymTBN/+twBBATr7iJNXf6K5akfruI1nccjbJntNR0iE/cypHqIISt" - + "AWzJAoIBAFDV5ZWkAIDm4EO+qpq5K2usk2/e49eDaIMd4qUHUXGMfCeVi1LvDjRA" - + "W2Sl0TYogqFF3+AoPjl9uj/RdHZQxto98H1yfwpwTs9CXErmRwRw9y2GIMj5LWBB" - + "aOQf0PUpgiFI2OrGf93cqHcLoD4WrPgmubnCnyxxa0o48Yrmy2Q/gB8vbSJ4fxxf" - + "92mbfbLBFNQaakeEKtbsXIZsADhtshHNPb1h7onuwy5S2sEsTlUegK77yCsDeVb3" - + "zBUH1WFsl257sGFRc/qvFYp4QuSfQxJA2BNiYaYUwjs+V1EWxitYACq206miSYCH" - + "v7xN9ntUS3cz2HNqrB/H1jN6aglnQOkCggEBAJb5FYvQCvw5PJM44nR6/U1cSlr4" - + "lRWcuFp7Xv5kWxSwM5115qic14fByh7DbaTHxxoPEhEA4aJ2QcDa7YWvabVc/VEV" - + "VacAAdg44+WSw6FNni18K53oOKAONgzSQlYUm/jgENIXi+5L0Yq7qAbnldiC6jXr" - + "yqbEwZjmpt8xsBLnl37k/LSLG1GUaYV8AK3s9UDs9/jv5RUrV96jiXed+7pYrjmj" - + "o1yJ4WAqouYHmOQCI3SeFCLT8GCdQ+uE74G5q+Yte6YT9jqSiGDjrst0bjtN640v" - + "YKRG3XK4AE9i4Oinnv/Ua95ql0syphn+CPW2ksmGon5/0mbK5qYsg47Hdls=" + "-----END RSA PRIVATE KEY-----"; - RsaRawEncryptor encryptor_4096 = new RsaRawEncryptor(pemData); - assertThat(encryptor_4096.decrypt(encryptor_4096.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - private static final String SHORT_STRING = "Bacon ipsum dolor sit amet tail pork loin pork chop filet mignon flank fatback tenderloin boudin shankle corned beef t-bone short ribs. Meatball capicola ball tip short loin beef ribs shoulder, kielbasa pork chop meatloaf biltong porchetta bresaola t-bone spare ribs. Andouille t-bone sausage ground round frankfurter venison. Ground round meatball chicken ribeye doner tongue porchetta."; - - private static String LONG_STRING; - -} diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptorTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptorTests.java deleted file mode 100644 index 48087a4e8d3..00000000000 --- a/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptorTests.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2013-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.crypto.encrypt; - -import java.security.PublicKey; -import java.security.interfaces.RSAPublicKey; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; - -/** - * @author Dave Syer - * - */ -public class RsaSecretEncryptorTests { - - private RsaSecretEncryptor encryptor = new RsaSecretEncryptor(); - - @BeforeEach - public void init() { - LONG_STRING = SHORT_STRING + SHORT_STRING + SHORT_STRING + SHORT_STRING; - for (int i = 0; i < 4; i++) { - LONG_STRING = LONG_STRING + LONG_STRING; - } - } - - @Test - public void roundTripKey() { - PublicKey key = RsaKeyHelper.generateKeyPair().getPublic(); - String encoded = RsaKeyHelper.encodePublicKey((RSAPublicKey) key, "application"); - assertThat(RsaKeyHelper.parsePublicKey(encoded)).isEqualTo(key); - } - - @Test - public void roundTrip() { - assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - @Test - public void roundTripWithSalt() { - this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, "somesalt"); - assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - @Test - public void roundTripWithHexSalt() { - this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, "beefea"); - assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - @Test - public void roundTripWithLongSalt() { - this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, "somesaltsomesaltsomesaltsomesaltsomesalt"); - assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - @Test - public void roundTripOaep() { - this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP); - assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - @Test - public void roundTripOaepGcm() { - this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, true); - assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - @Test - public void roundTripWithMixedAlgorithm() { - RsaSecretEncryptor oaep = new RsaSecretEncryptor(RsaAlgorithm.OAEP); - assertThatIllegalStateException().isThrownBy(() -> oaep.decrypt(this.encryptor.encrypt("encryptor"))); - } - - @Test - public void roundTripWithMixedSalt() { - RsaSecretEncryptor other = new RsaSecretEncryptor(this.encryptor.getPublicKey(), RsaAlgorithm.DEFAULT, "salt"); - assertThatIllegalStateException().isThrownBy(() -> this.encryptor.decrypt(other.encrypt("encryptor"))); - } - - @Test - public void roundTripWithPublicKeyEncryption() { - RsaSecretEncryptor encryptor = new RsaSecretEncryptor(this.encryptor.getPublicKey()); - RsaSecretEncryptor decryptor = this.encryptor; - assertThat(decryptor.decrypt(encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); - } - - @Test - public void publicKeyCannotDecrypt() { - RsaSecretEncryptor encryptor = new RsaSecretEncryptor(this.encryptor.getPublicKey()); - assertThat(encryptor.canDecrypt()).as("Encryptor schould not be able to decrypt").isFalse(); - assertThatIllegalStateException().isThrownBy(() -> encryptor.decrypt(encryptor.encrypt("encryptor"))); - } - - @Test - public void roundTripLongString() { - assertThat(this.encryptor.decrypt(this.encryptor.encrypt(LONG_STRING))).isEqualTo(LONG_STRING); - } - - private static final String SHORT_STRING = "Bacon ipsum dolor sit amet tail pork loin pork chop filet mignon flank fatback tenderloin boudin shankle corned beef t-bone short ribs. Meatball capicola ball tip short loin beef ribs shoulder, kielbasa pork chop meatloaf biltong porchetta bresaola t-bone spare ribs. Andouille t-bone sausage ground round frankfurter venison. Ground round meatball chicken ribeye doner tongue porchetta."; - - private static String LONG_STRING; - -} diff --git a/crypto/src/test/resources/bad.pem b/crypto/src/test/resources/bad.pem deleted file mode 100644 index 653a6eaea7d..00000000000 --- a/crypto/src/test/resources/bad.pem +++ /dev/null @@ -1,2 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAwClFgrRa/PUHPIJr9gvIPL6g6Rjp/TVZmVNOf2fL96DYbkj5 \ No newline at end of file diff --git a/crypto/src/test/resources/fake.pem b/crypto/src/test/resources/fake.pem deleted file mode 100644 index 931e57889d8..00000000000 --- a/crypto/src/test/resources/fake.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQDMWnfaQ0yLFXelprq2S8UurnaGvxFNUdbmTyJeycem5vGLycEY -T4KcdVCTU5491cjbk5GcHjoj2efRSO0y0aXIlUJpLofDdML/SuGLZWp/GbEv978M -pZIztK8iaIm7D/D7by8aws1RJyD9T+lZDAGY7eFfMp0EQyHOcEL0NGFLuwIDAQAB -AoGAWwC6uO8ZaiKwOouqQD4z3FsDG3SA/v7ABaYd9zpCd9gGnyrEm8/kqUoxDLrD -EGRg4y+vO2fWmlqSuoeQYf4spf+vi2di+mGIb6nGe7TpMLPa7lFLOSQHZRx5M5H6 -JDhfhAHlKmF9gLGvDHbpyErzn5YXjcu0PoFiNC1y445D8iECQQDvJzkGbJ9l9vb0 -oRyGXRDpddUcVMECLLB9NKmTl/zKy/qVPD+zYNoi87ePBJFbgmAXRjhhTk2uSBRP -NtVaMoXLAkEA2r+ugzjsLZQIYz/9gxdzdbKWDgpSPbhKCR4bOmrDgJMcOVjtwW+n -+liaX6zwI0QEgCAWLzCbbYDmj3kJrRwT0QJAaowg/dm7EmR7FfYJjVs9Q6X5skuY -Se27G60wt88JExjZpU9YWgSWaugGKbOxRwHI6dWhHMkUFseKNNiLKUpFDQJALIGP -ahdsxiE2S6s7Uy60SSAas6SZ8wDJ320GsS4DtOc5eNmFFjQ3gxH/5rNy8FnoaIEe -wl8rYG43er1voI7z4QJAB4qaqBo7eeiRgnUVIccaSZkNIMSrZ9QUjVFRgfLwAXDO -Ae+t6V+eB0oaIXczA+BLj3Oe6D3iHRGHrxGlcvDdHw== ------END RSA PRIVATE KEY----- diff --git a/crypto/src/test/resources/keystore.jks b/crypto/src/test/resources/keystore.jks deleted file mode 100644 index c13b189d0b81ec03ca47505736006008474929b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3174 zcmeH}XHXN^8irGm7J5@aFd`rb#ssA|g-`?p0qH0UNHq}31w=`VfPg3{O^uNjV39#X z2`yAXT#AAq9SjhfODNK7SoY4ndw0f}`|sXg{x~z|dw!jH=X=jQtOeEr0002}b$CNO zgG1asgF<|6`nbD=cme=GOr=-pB>>c3`8+}$002iK2;eXT0dzeJ3<82c5Dfqcf`GAc zD8w4aZo+^-E+_!ue~u5q!v?m6un8Wq#rR_3nrKM>9z2-gn;{1-tS7loZu zL8zh-NQ4R!iE{jNJcmH4{cHR`zlH#W|2+;M6aXdwc>xFlkP}1z0s$|5ahIOEbsxxS z)lsvW6)Qdbp6Kd1JsWoi(M?R+Y`f6EPlaJathnVr6nd&VB*N>OLRa-PIDBylmB~Vu z=DF+=)BnKtT>92|`38l@p3=7)fBH*2C8Rg*p82|V4Cx8xgBI4el%&o7+gCwd8+%*1 zG=){ZlzS=t^=<5W^=2bfzG|W>n6CqwTe58T+kJxYm6pwTrTUg3OKiBt8#Qi;Ja$ci zev5;Wjf;aA1M9M~9&hyOG-|FTS})QYuACp-Q8OU{A0sW4bfk-WWLLb0R(-(>4T&+U zxiXg~S}1RZ%8ZR$qfOpQir*qgcf>^q^aXN6+Z#TZ(wE99AKwOnfdF9c34|0v@^C{W zc_1PXA(Q*tDGb+P)y^Q+VuBdeT7SLt!{0+Z+^NG5fqZpaIX_#{53Z+wR}1p**{R`l><6c36#Bv3Gd0693U z8>b>05CT*H@dFQc4nly;Gq-w+`;zWfx1#%VqhW5ntAcy8H1E93f~NNo+L7Jivlgf7 z7pcws3odpZ1HrPPVzA(k=SR9^v8U|L%0VTrz~PNO#WrHaFXNO}mlSZfqv2+T_Qxcm zYZ+*=%Y-z8j@p?<6Zu%8EwniE|*bN!|G6$d3@*}B!`m3wZoKu4Pw zTOpw}8-its4EkNpJ*fr76uDS4Cqur@%AD`w<)0Dc_R0aH@sNYY0cJq@+{u=^X9KHG zJFr>P$9SCw;KU^_1!2gy0)%?cUT?>PPhrv-*JW$?oy&DjIv2 zg>(KFRZ^LjQ7X)wlW)44=gbsolk%cmXW&5(wrW=9=;J3!3|#{jhDaKYJ5i&rUu_=4 zMD5UY)=h^d@b5=ozuQfB#?ku58r?BjTpVqcKq*Pkl6PN2AYH^DmF9 zQMu*0W$J*h^m{*=aC=@DF+W|@VcKYGPVs3df-G{;Xk{}GqC}P8G4RfQYe(=q*(F7X zq|&hn^QLQ?ahqalNbSWADIJr_!K7WD${@qInHgl_hkoVI_-nV#z0Z?N*JTZ3>)d-G zx3l*&s)I8EzO=&d8mAYuME7Ci@N1DG0+HGbI$&x6^x87=pe8UHm)p9?br(va(R61>TfnDr7M*#tTSowAqqxa$gRk(JA(NxZE24cU#GQiP zW!78ZsIymcC$x^kh{;g(Vf8L(1&6_lF zy!E=R(a_XEI#sg;=E53b$eW+pUJBNCL_aT;Gi5y;?pT{>dF><_bXB|z)8KEc!;&^A z-~ZG&I+v=PJQUioXIyGQWfSsyHRD^XP*%xf_-WBvQ}30&rj*9U=lZRkSN1Oxq2QbdM)pEi(Z)R?M||+;P5ZnNx|mF2ewc4A!J59*(C|Ws3u(274KuB9w=mB zK-HlP25cMDNFAQ%9p-J8FbWO2q+;hzCl`LjZ0{zsJqt2Cib@@HS5Hmv+ZF`U}6HQe6 zf+-g8D>6uEB<0KHI*?^!a%-##^H`71e)koa<3=^xG#oQGI}^IHzXY-of4gGeov&<` zZfQ8IjM8|Y+!5X>on^9COWT{>KZl9!Y_Jr;;RcY(b(yaPOF+M-6=(der^$OxSI+P8 zBL@8}r@hKzzn`Yv!~)NtG_gG=e_z6w@A;0=s90jvcT~yAOf~1Jx>>)E4bEIc6*vkW zn{g-Wb6@x5w)EP}#SX5;W8z_5xBvMu{#2CzXx4ufrk{%PQ&Ij`ih^wQNFF!7lu^W- ObuAizJiMbxI`{*#yig(l diff --git a/crypto/src/test/resources/keystore.pkcs12 b/crypto/src/test/resources/keystore.pkcs12 deleted file mode 100644 index a6d7c0e7598561660aa4d9592e1889be52c06029..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3589 zcmY+EWmppqyT)N`jE2EzHbUu^7EnT(Nk|Duhrs9>A;N(D6{RJmq;sSL326or0@5QT z1e8V*Ci;5Lb-yc#bAP<0&~&5#QZgwtZ96%o2u2rkK}!lIEke`Q0MWD+ ze{qQv8sz&Q7RUjJ2HF0_R)5Egoce#hXsAd5MQ9*V3JtuF5+;Ue$btD3$#zBQB*>7#rpaQR&^P zTZNPvtYPqd@)ZN=vbY^?XxD_q2(HmE#X=dsA}OuROPITh%ADegtaMfhcu{ARI*n5E zKoiY)@iUOHgl4iq>=IuAxH17>Zw6&l(_Ag(|$SXKGjM*)pqW6v@N%sW0iC#d%sHy_AI;n!q3I!>?M*m zKyrb#DK^2;Kekyg4?& zR3yPyR1k1;L*wkr#2n3~0{ue9L)nRUg9-sowdlp{M;-$R{28U+X-9_D<=W*)9!HT$ zK8^+Z0`hKJ=2>ZG9l-OO-+Gj95a~p*;DH*&SZ~za=L(y6_ep(vWQ7StGhIBby+-bi zJWrc^Giei12%!3bf+nDS6Ur}CO}Zji4;4{BRDU>VoLJF)$uB&w-3$I+GnR!k8W&)dspApW5voFNRvQrj zE(vPpOwM(n+ypCLl>mh)iyRUU`W6(#PA3XT*|_Fn{GI zkz1MNnC7+`pf?nkF#jBNTfa<4mJ!!T_Kdp^RQn%U{>%pGujUP?LxQ>J!UbfsS zXYcF;eIE=SYR(^U&f+NJl5_t>31kP=@@rurjUT!;a5=(vJO!V7S^vrt^3GR1N~7oU2$yN?5xih*<1d*NSLsBaF1W1@DzamtUWzf)Ujv7`!Bocc646**pVOQCE7 z7?Q{K1LAXi6Mlu&6XoRP$vB#b18#FZrQ@AmquVgLVxSsDstH1qIw<&ljS@Qqt**ON$8s4Rg+V#^yz-}<$i;BZ5o&WCH zXf$}?z5jDA7F8`ruK*c;89kzeCR=;c$fZkQiZci6&_dh}1u;tmq-!Z6o^3o!dDvcF z)JUBR>Fc?6FFnSdZGK;*?61roj+~ z$f2rlafo?fuk2EJKt?p&(`Jj)u_coylZ6?yy`%4aRlL*yhL+vCoT2UJBT zJuwEu6*|mMsByf%E+KsuFviluLXnRmf{#$R#ffX+9|Kb^%4@5tetdA#del8#=M-|x z_aa)*xvrFM!uxq;qbZ!)k%U?OuDSftPXZ=MQ@Z+vvVp%L7QS0Z_wA2tv9U_^ zwHvW*E#N}6RTK}yBkZZuo_)c&@O;e+;JNTKBU-3VcyhXhyf@-p^EH!4JuYM$a!i|r z3CdhR9|TD~e(T=Qi2X?+lztJv(9xN|bKbLl8tT!irfWr(O_SZx!I2o8z(*#ow{|!G z#ev@&INhX7S(av9h=zATLekZ{d*;FMh(}{Ev>YlBwV9%F*vDw55%z?gnA$NqjWOQb zmv2XEhkJCu9lwZ^JvV}JY z>k}a0vXcr)LN9}42j;EN>9F(UxT`@-se5e|^KJS2#bDATf=_tVew8C>1&FR^XEW%=h;16P}Yz9mxp zhE%9&Q)st$%g<;zG2_J{X{H3;lL`xijlKE{PDHtv^szr)ICN8RI!fm&8SOP%l zi&UPbE|_;X?}`dY@>c6?)a&OQ%@XParl>Ab6#%Vn-VYA9a)d84;qWBVF^44*qQB_I zVdMo9KBi)Y14@E9wq2q1lvM@K8qc5<%!VW4(!zx z9nt8^&1M|sq4mw=2|-SSLDbRF&tk8FujYk&RRZQ`NP-}8XU!|&(+Ynt&)aQ=-J8<9 zBB+m_Dh%uIyvh$UPbt|82*U=Mlh3rI+;s> z+1~QC@kx`y=3zq;!f7ymZv#KvwICMsKDTpSeQ7sR`L0~Vm<6tjX_moj|1)e@jt||l zA=gr(a|{=3q>Q<%Z|YdZw(J!2%$+%RKCNT!-i`z-2bH{fE+l2lx#k+u5Ug?1$qti% z)K68Fc~R6f3CF&`$OI&e;#MuOBaREU|Wy)+AJ! z^Ep8t-$K@ld;|}=!aXg4;Fu-TJ?cwn`8BZd=1w#nm^z+jJPfdKXv=H9`66S!WuV4= ze9y-HM+XFIOonE;$$cLG;3S7#Kx5E87JTq@J(_(r_nPW% zZpIPWUe0$aJrT(B>t>`9k5UbVz3 z`jgG=G~KDSFu%xhZfBjDt5zVoVTbKsC63h!-0x^m2#dEjIx`JEV5CmOEqlsz^XLEq zQx4t~*41-LBqHaNmy(i8odp~8u88hO1Q(nhiHYI}XapF66?%nuss+xAd%g)HsN$(^ z&6(g!{_(=r`TsurX{f3jKiwG#V!aTo$pqUK{wZxwihqqXtEJOFLYyJkkkua^!Ef>S za(K@owS(C7#= z>QhFDn|y?;cfBTb-?o_D@BHpDtJ6z}?yNqlpHw#1k?wV=Ze!xSFhGHI>G}GVOKX=c z6s@@LNx#(2RI>V!`-`RKxN<+4Z&+YF(HvH^bHbCZ29mlX#VbWg4icp!18{&yfKYl+ xX#(5YI-?v4rywG(C8PDRE)xjVj7Ve@H69xzV<$Vf3o{{l`4rE&lO diff --git a/crypto/src/test/resources/spacey.pem b/crypto/src/test/resources/spacey.pem deleted file mode 100644 index 1050b1dc1aa..00000000000 --- a/crypto/src/test/resources/spacey.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAwClFgrRa/PUHPIJr9gvIPL6g6Rjp/TVZmVNOf2fL96DYbkj5 -4YbrwfKwjoTjk1M6gLQpOA4Blocx6zN5OnICnVGlVM9xymWxTxxCfc2tE2Fai9I1wchULCChhwm/UU5ZNi3KpXinlyamSYw+lMQkZ8gTXCgOEvs2j9E1quF4pvy1BZKvbD8tUnUQlyiKRnI6gOxQL8B6OAYPRdaa9FVNmrs1B4eDPG918L2f1pT090P1n+tw -iejNgQvtSD78/A88qt89OhzscsufALTrBjycn89kkfBd0zbVLF0W6+ZVLZrf97/y -LCoGSCcZL9LFPNvNqxOnleviDco7aOs4stQ9jQIDAQABAoIBAQC1TbthyN0YUe+T - 7dIDAbbZaVrU00biOtXgzjMADmTprP7Hf18UpIIIKfzfWw6FUD+gc1t4oe5pogE9 -UwGMXUmOORxu2pMYTb5vT9CEdexYnsAZsCo8PdD9GYSNrmquQef2MFpEqYQmHrdC - KWpaXn2i1ak+iCRPUGp4YwHpynZVxfE8z/AIsPn6NPDh6SnCXb1rTgQe2UCfXm93 -UJe5F/OR2kQi5KFO+dxLmCOBCwr6SGCLH+VotGpuxCVRUd9sJ/d4QpDZEgjuf7Ug -eQHfgMDS/tc09B9rl0dwKnEa31kcQ9X9KLkKP+w0Pqhh0Emny20eg9jS6XNayg61 -p/LQtW9BAoGBAO5veKMIcXfZmuh11WIIhdhLKkNtYyt5NDmrV8/IVScLFvjB0ftt -8PAtXo/ekOHkyITyIumQ9l4VCvacNw7DyV9FYk4WvrvVYOCL8aZi+O5+12NT67eO -Rr/voGlRoV05X7+inc90qbbYJ8lRmLSqvzmsm98mkuhw/FKGRhVZIfAJAoGBAM5R - I5vK6cJxOwXQOEGOd5/8B9JMFXyuendXo/N2/NxSQsbx4pc3v2rv/eGJYaY7Nx/y -2M/vdWYkpG59PAS3k2TrCA/0SGmyVqY+c8BomKisU5VaBlIPfGuec9tDPgWCp8Ur -3Jjt/2sVoa0vMkqymUqMb9HyH9tdI9oyh7EOOrplAoGAR6DlNNUMgVy11K/Rcqns -y5WJFMh/ykeXENwQfTNJoXkLZZ+UXVwhzYVTqxTJoZMBSi8TnecWnBzmNj+nqp/W - lvBZH+xlUDhB6jMgXUPOVJd2TTigz3vGdVKfdgQ33bGmugM4NWJuuacmDKyem2fQ - GptoGBmWeI24v3HnC/LC50ECgYAz0iN8hRnz0db+Xc9TgAJB997LDnszJuvxv9yZ - UWCvwiWtrKG6U7FLnd4J4STayPLOnoOgrsexETEP43rIwIdQCMysnTH3AmlLNlKC - mIMHksknsUX3JJaevVziTOBuJ+QV3S96ZgUKk5NZWYprQrLIC8AmXodr5NgVfS2h - 5i4QFQKBgFfbYHiMw5AAUQrBNkrAjLd1wIaO/6qS3w4OsCWKowhfaJLEXAbIRV7s -vAtgtlCovdasVj4RRLXFf+73naVTQjBZI+3jWHHyFk3+Zy86mQCSGv9WuDVV1IhS -h8InTVvK8wgdgX7qiw3pvU0roqNW4/j4j8OqJO3Zt4KO2iX8htsO ------END RSA PRIVATE KEY----- diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 9dd47794062..bc297593522 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -129,8 +129,6 @@ ** Authentication *** xref:reactive/authentication/x509.adoc[X.509 Authentication] *** xref:reactive/authentication/logout.adoc[Logout] -*** Session Management -**** xref:reactive/authentication/concurrent-sessions-control.adoc[Concurrent Sessions Control] ** Authorization *** xref:reactive/authorization/authorize-http-requests.adoc[Authorize HTTP Requests] *** xref:reactive/authorization/method.adoc[EnableReactiveMethodSecurity] diff --git a/docs/modules/ROOT/pages/features/authentication/password-storage.adoc b/docs/modules/ROOT/pages/features/authentication/password-storage.adoc index c10967b7c44..08aa0778ede 100644 --- a/docs/modules/ROOT/pages/features/authentication/password-storage.adoc +++ b/docs/modules/ROOT/pages/features/authentication/password-storage.adoc @@ -591,99 +591,3 @@ http { ====== With the above configuration, when a password manager navigates to `/.well-known/change-password`, then Spring Security will redirect to `/update-password`. - -[[authentication-compromised-password-check]] -== Compromised Password Checking - -There are some scenarios where you need to check whether a password has been compromised, for example, if you are creating an application that deals with sensitive data, it is often needed that you perform some check on user's passwords in order to assert its reliability. -One of these checks can be if the password has been compromised, usually because it has been found in a https://wikipedia.org/wiki/Data_breach[data breach]. - -To facilitate that, Spring Security provides integration with the https://haveibeenpwned.com/API/v3#PwnedPasswords[Have I Been Pwned API] via the {security-api-url}org/springframework/security/core/password/HaveIBeenPwnedRestApiPasswordChecker.html[`HaveIBeenPwnedRestApiPasswordChecker` implementation] of the {security-api-url}org/springframework/security/core/password/CompromisedPasswordChecker.html[`CompromisedPasswordChecker` interface]. - -You can either use the `CompromisedPasswordChecker` API by yourself or, if you are using xref:servlet/authentication/passwords/dao-authentication-provider.adoc[the `DaoAuthenticationProvider]` via xref:servlet/authentication/passwords/index.adoc[Spring Security authentication mechanisms], you can provide a `CompromisedPasswordChecker` bean, and it will be automatically picked up by Spring Security configuration. - -.Using CompromisedPasswordChecker as a bean -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests(authorize -> authorize - .anyRequest().authenticated() - ) - .formLogin(withDefaults()) - .httpBasic(withDefaults()); - return http.build(); -} - -@Bean -public CompromisedPasswordChecker compromisedPasswordChecker() { - return new HaveIBeenPwnedRestApiPasswordChecker(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun filterChain(http:HttpSecurity): SecurityFilterChain { - http { - authorizeHttpRequests { - authorize(anyRequest, authenticated) - } - formLogin {} - httpBasic {} - } - return http.build() -} - -@Bean -open fun compromisedPasswordChecker(): CompromisedPasswordChecker { - return HaveIBeenPwnedRestApiPasswordChecker() -} ----- -====== - -By doing that, when you try to authenticate via HTTP Basic or Form Login using a weak password, let's say `123456`, you will receive a 401 response status code. -However, just a 401 is not so useful in that case, it will cause some confusion because the user provided the right password and still was not allowed to log in. -In such cases, you can handle the `CompromisedPasswordException` to perform your desired logic, like redirecting the user-agent to `/reset-password`, for example: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@ControllerAdvice -public class MyControllerAdvice { - - @ExceptionHandler(CompromisedPasswordException.class) - public String handleCompromisedPasswordException(CompromisedPasswordException ex, RedirectAttributes attributes) { - attributes.addFlashAttribute("error", ex.message); - return "redirect:/reset-password"; - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@ControllerAdvice -class MyControllerAdvice { - - @ExceptionHandler(CompromisedPasswordException::class) - fun handleCompromisedPasswordException(ex: CompromisedPasswordException, attributes: RedirectAttributes): RedirectView { - attributes.addFlashAttribute("error", ex.message) - return RedirectView("/reset-password") - } - -} ----- -====== diff --git a/docs/modules/ROOT/pages/reactive/authentication/concurrent-sessions-control.adoc b/docs/modules/ROOT/pages/reactive/authentication/concurrent-sessions-control.adoc deleted file mode 100644 index eb313ec52c2..00000000000 --- a/docs/modules/ROOT/pages/reactive/authentication/concurrent-sessions-control.adoc +++ /dev/null @@ -1,458 +0,0 @@ -[[reactive-concurrent-sessions-control]] -= Concurrent Sessions Control - -Similar to xref:servlet/authentication/session-management.adoc#ns-concurrent-sessions[Servlet's Concurrent Sessions Control], Spring Security also provides support to limit the number of concurrent sessions a user can have in a Reactive application. - -When you set up Concurrent Sessions Control in Spring Security, it monitors authentications carried out through Form Login, xref:reactive/oauth2/login/index.adoc[OAuth 2.0 Login], and HTTP Basic authentication by hooking into the way those authentication mechanisms handle authentication success. -More specifically, the session management DSL will add the {security-api-url}org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.html[ConcurrentSessionControlServerAuthenticationSuccessHandler] and the {security-api-url}org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.html[RegisterSessionServerAuthenticationSuccessHandler] to the list of `ServerAuthenticationSuccessHandler` used by the authentication filter. - -The following sections contains examples of how to configure Concurrent Sessions Control. - -* <> -* <> -* <> -* <> -* <> - -[[reactive-concurrent-sessions-control-limit]] -== Limiting Concurrent Sessions - -By default, Spring Security will allow any number of concurrent sessions for a user. -To limit the number of concurrent sessions, you can use the `maximumSessions` DSL method: - -.Configuring one session for any user -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - http - // ... - .sessionManagement((sessions) -> sessions - .concurrentSessions((concurrency) -> concurrency - .maximumSessions(SessionLimit.of(1)) - ) - ); - return http.build(); -} - -@Bean -ReactiveSessionRegistry reactiveSessionRegistry() { - return new InMemoryReactiveSessionRegistry(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - // ... - sessionManagement { - sessionConcurrency { - maximumSessions = SessionLimit.of(1) - } - } - } -} -@Bean -open fun reactiveSessionRegistry(): ReactiveSessionRegistry { - return InMemoryReactiveSessionRegistry() -} ----- -====== - -The above configuration allows one session for any user. -Similarly, you can also allow unlimited sessions by using the `SessionLimit#UNLIMITED` constant: - -.Configuring unlimited sessions -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - http - // ... - .sessionManagement((sessions) -> sessions - .concurrentSessions((concurrency) -> concurrency - .maximumSessions(SessionLimit.UNLIMITED)) - ); - return http.build(); -} - -@Bean -ReactiveSessionRegistry reactiveSessionRegistry() { - return new InMemoryReactiveSessionRegistry(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - // ... - sessionManagement { - sessionConcurrency { - maximumSessions = SessionLimit.UNLIMITED - } - } - } -} -@Bean -open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry { - return InMemoryReactiveSessionRegistry() -} ----- -====== - -Since the `maximumSessions` method accepts a `SessionLimit` interface, which in turn extends `Function>`, you can have a more complex logic to determine the maximum number of sessions based on the user's authentication: - -.Configuring maximumSessions based on `Authentication` -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - http - // ... - .sessionManagement((sessions) -> sessions - .concurrentSessions((concurrency) -> concurrency - .maximumSessions(maxSessions())) - ); - return http.build(); -} - -private SessionLimit maxSessions() { - return (authentication) -> { - if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) { - return Mono.empty(); // allow unlimited sessions for users with ROLE_UNLIMITED_SESSIONS - } - if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) { - return Mono.just(2); // allow two sessions for admins - } - return Mono.just(1); // allow one session for every other user - }; -} - -@Bean -ReactiveSessionRegistry reactiveSessionRegistry() { - return new InMemoryReactiveSessionRegistry(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - // ... - sessionManagement { - sessionConcurrency { - maximumSessions = maxSessions() - } - } - } -} - -fun maxSessions(): SessionLimit { - return { authentication -> - if (authentication.authorities.contains(SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) Mono.empty - if (authentication.authorities.contains(SimpleGrantedAuthority("ROLE_ADMIN"))) Mono.just(2) - Mono.just(1) - } -} - -@Bean -open fun reactiveSessionRegistry(): ReactiveSessionRegistry { - return InMemoryReactiveSessionRegistry() -} ----- -====== - -When the maximum number of sessions is exceeded, by default, the least recently used session(s) will be expired. -If you want to change that behavior, you can <>. - -[[concurrent-sessions-control-custom-strategy]] -== Handling Maximum Number of Sessions Exceeded - -By default, when the maximum number of sessions is exceeded, the least recently used session(s) will be expired by using the {security-api-url}org/springframework/security/web/server/authentication/session/InvalidateLeastUsedMaximumSessionsExceededHandler.html[InvalidateLeastUsedMaximumSessionsExceededHandler]. -Spring Security also provides another implementation that prevents the user from creating new sessions by using the {security-api-url}org/springframework/security/web/server/authentication/session/PreventLoginMaximumSessionsExceededHandler.html[PreventLoginMaximumSessionsExceededHandler]. -If you want to use your own strategy, you can provide a different implementation of {security-api-url}org/springframework/security/web/server/authentication/session/ServerMaximumSessionsExceededHandler.html[ServerMaximumSessionsExceededHandler]. - -.Configuring maximumSessionsExceededHandler -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - http - // ... - .sessionManagement((sessions) -> sessions - .concurrentSessions((concurrency) -> concurrency - .maximumSessions(SessionLimit.of(1)) - .maximumSessionsExceededHandler(new PreventLoginMaximumSessionsExceededHandler()) - ) - ); - return http.build(); -} - -@Bean -ReactiveSessionRegistry reactiveSessionRegistry() { - return new InMemoryReactiveSessionRegistry(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - // ... - sessionManagement { - sessionConcurrency { - maximumSessions = SessionLimit.of(1) - maximumSessionsExceededHandler = PreventLoginMaximumSessionsExceededHandler() - } - } - } -} - -@Bean -open fun reactiveSessionRegistry(): ReactiveSessionRegistry { - return InMemoryReactiveSessionRegistry() -} ----- -====== - -[[reactive-concurrent-sessions-control-specify-session-registry]] -== Specifying a `ReactiveSessionRegistry` - -In order to keep track of the user's sessions, Spring Security uses a {security-api-url}org/springframework/security/core/session/ReactiveSessionRegistry.html[ReactiveSessionRegistry], and, every time a user logs in, their session information is saved. - -Spring Security ships with {security-api-url}org/springframework/security/core/session/InMemoryReactiveSessionRegistry.html[InMemoryReactiveSessionRegistry] implementation of `ReactiveSessionRegistry`. - -To specify a `ReactiveSessionRegistry` implementation you can either declare it as a bean: - -.ReactiveSessionRegistry as a Bean -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - http - // ... - .sessionManagement((sessions) -> sessions - .concurrentSessions((concurrency) -> concurrency - .maximumSessions(SessionLimit.of(1)) - ) - ); - return http.build(); -} - -@Bean -ReactiveSessionRegistry reactiveSessionRegistry() { - return new MyReactiveSessionRegistry(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - // ... - sessionManagement { - sessionConcurrency { - maximumSessions = SessionLimit.of(1) - } - } - } -} - -@Bean -open fun reactiveSessionRegistry(): ReactiveSessionRegistry { - return MyReactiveSessionRegistry() -} ----- -====== - -or you can use the `sessionRegistry` DSL method: - -.ReactiveSessionRegistry using sessionRegistry DSL method -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - http - // ... - .sessionManagement((sessions) -> sessions - .concurrentSessions((concurrency) -> concurrency - .maximumSessions(SessionLimit.of(1)) - .sessionRegistry(new MyReactiveSessionRegistry()) - ) - ); - return http.build(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - // ... - sessionManagement { - sessionConcurrency { - maximumSessions = SessionLimit.of(1) - sessionRegistry = MyReactiveSessionRegistry() - } - } - } -} ----- -====== - -[[reactive-concurrent-sessions-control-manually-invalidating-sessions]] -== Invalidating Registered User's Sessions - -At times, it is handy to be able to invalidate all or some of a user's sessions. -For example, when a user changes their password, you may want to invalidate all of their sessions so that they are forced to log in again. -To do that, you can use the `ReactiveSessionRegistry` bean to retrieve all the user's sessions, invalidate them, and them remove them from the `WebSessionStore`: - -.Using ReactiveSessionRegistry to invalidate sessions manually -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -public class SessionControl { - private final ReactiveSessionRegistry reactiveSessionRegistry; - - private final WebSessionStore webSessionStore; - - public Mono invalidateSessions(String username) { - return this.reactiveSessionRegistry.getAllSessions(username) - .flatMap((session) -> session.invalidate().thenReturn(session)) - .flatMap((session) -> this.webSessionStore.removeSession(session.getSessionId())) - .then(); - } -} ----- -====== - -[[disabling-for-authentication-filters]] -== Disabling It for Some Authentication Filters - -By default, Concurrent Sessions Control will be configured automatically for Form Login, OAuth 2.0 Login, and HTTP Basic authentication as long as they do not specify an `ServerAuthenticationSuccessHandler` themselves. -For example, the following configuration will disable Concurrent Sessions Control for Form Login: - -.Disabling Concurrent Sessions Control for Form Login -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - http - // ... - .formLogin((login) -> login - .authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/")) - ) - .sessionManagement((sessions) -> sessions - .concurrentSessions((concurrency) -> concurrency - .maximumSessions(SessionLimit.of(1)) - ) - ); - return http.build(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - // ... - formLogin { - authenticationSuccessHandler = RedirectServerAuthenticationSuccessHandler("/") - } - sessionManagement { - sessionConcurrency { - maximumSessions = SessionLimit.of(1) - } - } - } -} ----- -====== - -=== Adding Additional Success Handlers Without Disabling Concurrent Sessions Control - -You can also include additional `ServerAuthenticationSuccessHandler` instances to the list of handlers used by the authentication filter without disabling Concurrent Sessions Control. -To do that you can use the `authenticationSuccessHandler(Consumer>)` method: - -.Adding additional handlers -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - http - // ... - .formLogin((login) -> login - .authenticationSuccessHandler((handlers) -> handlers.add(new MyAuthenticationSuccessHandler())) - ) - .sessionManagement((sessions) -> sessions - .concurrentSessions((concurrency) -> concurrency - .maximumSessions(SessionLimit.of(1)) - ) - ); - return http.build(); -} ----- -====== - -[[concurrent-sessions-control-sample]] -== Checking a Sample Application - -You can check the {gh-samples-url}/reactive/webflux/java/session-management/maximum-sessions[sample application here]. diff --git a/docs/modules/ROOT/pages/reactive/authorization/method.adoc b/docs/modules/ROOT/pages/reactive/authorization/method.adoc index aebabef9874..61da6135cfd 100644 --- a/docs/modules/ROOT/pages/reactive/authorization/method.adoc +++ b/docs/modules/ROOT/pages/reactive/authorization/method.adoc @@ -19,7 +19,7 @@ This improves upon `@EnableReactiveMethodSecurity` in a number of ways. `@Enable 1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters. This simplifies reuse and customization. -2. Supports reactive return types including Kotlin coroutines. +2. Supports reactive return types. Note that we are waiting on https://github.com/spring-projects/spring-framework/issues/22462[additional coroutine support from the Spring Framework] before adding coroutine support. 3. Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize 4. Checks for conflicting annotations to ensure an unambiguous security configuration 5. Complies with JSR-250 @@ -304,6 +304,13 @@ and it will be invoked after the `@PostAuthorize` interceptor. == EnableReactiveMethodSecurity +[WARNING] +==== +`@EnableReactiveMethodSecurity` also supports Kotlin coroutines, though only to a limited degree. +When intercepting coroutines, only the first interceptor participates. +If any other interceptors are present and come after Spring Security's method security interceptor, https://github.com/spring-projects/spring-framework/issues/22462[they will be skipped]. +==== + [tabs] ====== Java:: diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc index 2a3e406115b..450485a6466 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc @@ -22,7 +22,7 @@ The `OAuth2AuthorizationRequestRedirectWebFilter` uses a `ServerOAuth2Authorizat The primary role of the `ServerOAuth2AuthorizationRequestResolver` is to resolve an `OAuth2AuthorizationRequest` from the provided web request. The default implementation `DefaultServerOAuth2AuthorizationRequestResolver` matches on the (default) path `+/oauth2/authorization/{registrationId}+` extracting the `registrationId` and using it to build the `OAuth2AuthorizationRequest` for the associated `ClientRegistration`. -Given the following Spring Boot properties for an OAuth 2.0 Client registration: +Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml,attrs="-attributes"] ---- @@ -107,9 +107,7 @@ For example, OpenID Connect defines additional OAuth 2.0 request parameters for One of those extended parameters is the `prompt` parameter. [NOTE] -==== -The `prompt` parameter is optional. Space delimited, case sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for re-authentication and consent. The defined values are: `none`, `login`, `consent`, and `select_account`. -==== +OPTIONAL. Space delimited, case sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for reauthentication and consent. The defined values are: none, login, consent, select_account The following example shows how to configure the `DefaultServerOAuth2AuthorizationRequestResolver` with a `Consumer` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`. @@ -575,7 +573,7 @@ which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the === Using the Access Token -Given the following Spring Boot properties for an OAuth 2.0 Client registration: +Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml] ---- @@ -782,7 +780,7 @@ which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the === Using the Access Token -Given the following Spring Boot properties for an OAuth 2.0 Client registration: +Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml] ---- @@ -1035,7 +1033,7 @@ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) === Using the Access Token -Given the following Spring Boot properties for an OAuth 2.0 Client registration: +Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml] ---- @@ -1158,215 +1156,3 @@ class OAuth2ResourceServerController { [TIP] If you need to resolve the `Jwt` assertion from a different source, you can provide `JwtBearerReactiveOAuth2AuthorizedClientProvider.setJwtAssertionResolver()` with a custom `Function>`. - -[[oauth2Client-token-exchange-grant]] -== Token Exchange - -[NOTE] -Please refer to OAuth 2.0 Token Exchange for further details on the https://datatracker.ietf.org/doc/html/rfc8693[Token Exchange] grant. - - -=== Requesting an Access Token - -[NOTE] -Please refer to the https://datatracker.ietf.org/doc/html/rfc8693#section-2[Token Exchange Request and Response] protocol flow for the Token Exchange grant. - -The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Token Exchange grant is `WebClientReactiveTokenExchangeTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server’s Token Endpoint. - -The `WebClientReactiveTokenExchangeTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response. - - -=== Customizing the Access Token Request - -If you need to customize the pre-processing of the Token Request, you can provide `WebClientReactiveTokenExchangeTokenResponseClient.setParametersConverter()` with a custom `Converter>`. -The default implementation builds a `MultiValueMap` containing only the `grant_type` parameter of a standard https://tools.ietf.org/html/rfc6749#section-4.4.2[OAuth 2.0 Access Token Request] which is used to construct the request. -Other parameters required by the Token Exchange grant are added directly to the body of the request by the `WebClientReactiveTokenExchangeTokenResponseClient`. -However, providing a custom `Converter`, would allow you to extend the standard Token Request and add custom parameter(s). - -[TIP] -If you prefer to only add additional parameters, you can instead provide `WebClientReactiveTokenExchangeTokenResponseClient.addParametersConverter()` with a custom `Converter>` which constructs an aggregate `Converter`. - -IMPORTANT: The custom `Converter` must return valid parameters of an OAuth 2.0 Access Token Request that is understood by the intended OAuth 2.0 Provider. - -=== Customizing the Access Token Response - -On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `WebClientReactiveTokenExchangeTokenResponseClient.setBodyExtractor()` with a custom configured `BodyExtractor, ReactiveHttpInputMessage>` that is used for converting the OAuth 2.0 Access Token Response to an `OAuth2AccessTokenResponse`. -The default implementation provided by `OAuth2BodyExtractors.oauth2AccessTokenResponse()` parses the response and handles errors accordingly. - -=== Customizing the `WebClient` - -Alternatively, if your requirements are more advanced, you can take full control of the request/response by simply providing `WebClientReactiveTokenExchangeTokenResponseClient.setWebClient()` with a custom configured `WebClient`. - -Whether you customize `WebClientReactiveTokenExchangeTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -// Customize -ReactiveOAuth2AccessTokenResponseClient tokenExchangeTokenResponseClient = ... - -TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider(); -tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient); - -ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = - ReactiveOAuth2AuthorizedClientProviderBuilder.builder() - .provider(tokenExchangeAuthorizedClientProvider) - .build(); - -... - -authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -// Customize -val tokenExchangeTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient = ... - -val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider() -tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient) - -val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder() - .provider(tokenExchangeAuthorizedClientProvider) - .build() - -... - -authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) ----- -====== - -=== Using the Access Token - -Given the following Spring Boot properties for an OAuth 2.0 Client registration: - -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: - okta: - client-id: okta-client-id - client-secret: okta-client-secret - authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange - scope: read - provider: - okta: - token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token ----- - -...and the `OAuth2AuthorizedClientManager` `@Bean`: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( - ReactiveClientRegistrationRepository clientRegistrationRepository, - ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { - - TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = - new TokenExchangeReactiveOAuth2AuthorizedClientProvider(); - - ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = - ReactiveOAuth2AuthorizedClientProviderBuilder.builder() - .provider(tokenExchangeAuthorizedClientProvider) - .build(); - - DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = - new DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); - - return authorizedClientManager; -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun authorizedClientManager( - clientRegistrationRepository: ReactiveClientRegistrationRepository, - authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager { - val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider() - val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder() - .provider(tokenExchangeAuthorizedClientProvider) - .build() - val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository) - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) - return authorizedClientManager -} ----- -====== - -You may obtain the `OAuth2AccessToken` as follows: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@RestController -public class OAuth2ResourceServerController { - - @Autowired - private ReactiveOAuth2AuthorizedClientManager authorizedClientManager; - - @GetMapping("/resource") - public Mono resource(JwtAuthenticationToken jwtAuthentication) { - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta") - .principal(jwtAuthentication) - .build(); - - return this.authorizedClientManager.authorize(authorizeRequest) - .map(OAuth2AuthorizedClient::getAccessToken) - ... - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -class OAuth2ResourceServerController { - - @Autowired - private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager - - @GetMapping("/resource") - fun resource(jwtAuthentication: JwtAuthenticationToken): Mono { - val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta") - .principal(jwtAuthentication) - .build() - return authorizedClientManager.authorize(authorizeRequest) - .map { it.accessToken } - ... - } -} ----- -====== - -[NOTE] -`TokenExchangeReactiveOAuth2AuthorizedClientProvider` resolves the subject token (as an `OAuth2Token`) via `OAuth2AuthorizationContext.getPrincipal().getPrincipal()` by default, hence the use of `JwtAuthenticationToken` in the preceding example. -An actor token is not resolved by default. - -[TIP] -If you need to resolve the subject token from a different source, you can provide `TokenExchangeReactiveOAuth2AuthorizedClientProvider.setSubjectTokenResolver()` with a custom `Function>`. - -[TIP] -If you need to resolve an actor token, you can provide `TokenExchangeReactiveOAuth2AuthorizedClientProvider.setActorTokenResolver()` with a custom `Function>`. diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/client-authentication.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/client-authentication.adoc index 3bcf7c778f0..f54d06caa9e 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/client-authentication.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/client-authentication.adoc @@ -18,7 +18,7 @@ is supplied by the `com.nimbusds.jose.jwk.JWK` resolver associated with `NimbusJ === Authenticate using `private_key_jwt` -Given the following Spring Boot properties for an OAuth 2.0 Client registration: +Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml] ---- @@ -89,7 +89,7 @@ tokenResponseClient.addParametersConverter( === Authenticate using `client_secret_jwt` -Given the following Spring Boot properties for an OAuth 2.0 Client registration: +Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml] ---- diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc index 25b2d2b7fb3..43a9f33199e 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc @@ -59,7 +59,7 @@ The name may be used in certain scenarios, such as when displaying the name of t which contains the cryptographic key(s) used to verify the https://tools.ietf.org/html/rfc7515[JSON Web Signature (JWS)] of the ID Token and optionally the UserInfo Response. <12> `issuerUri`: Returns the issuer identifier uri for the OpenID Connect 1.0 provider or the OAuth 2.0 Authorization Server. <13> `configurationMetadata`: The https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Provider Configuration Information]. - This information will only be available if the Spring Boot property `spring.security.oauth2.client.provider.[providerId].issuerUri` is configured. + This information will only be available if the Spring Boot 2.x property `spring.security.oauth2.client.provider.[providerId].issuerUri` is configured. <14> `(userInfoEndpoint)uri`: The UserInfo Endpoint URI used to access the claims/attributes of the authenticated end-user. <15> `(userInfoEndpoint)authenticationMethod`: The authentication method used when sending the access token to the UserInfo Endpoint. The supported values are *header*, *form* and *query*. @@ -100,7 +100,7 @@ The `ReactiveClientRegistrationRepository` serves as a repository for OAuth 2.0 Client registration information is ultimately stored and owned by the associated Authorization Server. This repository provides the ability to retrieve a sub-set of the primary client registration information, which is stored with the Authorization Server. -Spring Boot auto-configuration binds each of the properties under `spring.security.oauth2.client.registration._[registrationId]_` to an instance of `ClientRegistration` and then composes each of the `ClientRegistration` instance(s) within a `ReactiveClientRegistrationRepository`. +Spring Boot 2.x auto-configuration binds each of the properties under `spring.security.oauth2.client.registration._[registrationId]_` to an instance of `ClientRegistration` and then composes each of the `ClientRegistration` instance(s) within a `ReactiveClientRegistrationRepository`. [NOTE] The default implementation of `ReactiveClientRegistrationRepository` is `InMemoryReactiveClientRegistrationRepository`. @@ -213,7 +213,7 @@ class OAuth2ClientController { ====== [NOTE] -Spring Boot auto-configuration registers an `ServerOAuth2AuthorizedClientRepository` and/or `ReactiveOAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`. +Spring Boot 2.x auto-configuration registers an `ServerOAuth2AuthorizedClientRepository` and/or `ReactiveOAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`. However, the application may choose to override and register a custom `ServerOAuth2AuthorizedClientRepository` or `ReactiveOAuth2AuthorizedClientService` `@Bean`. The default implementation of `ReactiveOAuth2AuthorizedClientService` is `InMemoryReactiveOAuth2AuthorizedClientService`, which stores `OAuth2AuthorizedClient`(s) in-memory. diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/index.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/index.adoc index df7bfb1d41e..a0c2d5bc9c1 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/index.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/index.adoc @@ -12,13 +12,12 @@ At a high-level, the core features available are: * https://tools.ietf.org/html/rfc6749#section-1.3.4[Client Credentials] * https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials] * https://datatracker.ietf.org/doc/html/rfc7523#section-2.1[JWT Bearer] -* https://datatracker.ietf.org/doc/html/rfc8693#section-2.1[Token Exchange] .Client Authentication support * https://datatracker.ietf.org/doc/html/rfc7523#section-2.2[JWT Bearer] .HTTP Client support -* xref:reactive/oauth2/client/authorized-clients.adoc#oauth2Client-webclient-webflux[`WebClient` integration for Reactive Environments] (for requesting protected resources) +* <> (for requesting protected resources) The `ServerHttpSecurity.oauth2Client()` DSL provides a number of configuration options for customizing the core components used by OAuth 2.0 Client. diff --git a/docs/modules/ROOT/pages/reactive/oauth2/index.adoc b/docs/modules/ROOT/pages/reactive/oauth2/index.adoc index 0139224ad68..592bc9fbab7 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/index.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/index.adoc @@ -1,1616 +1,8 @@ [[webflux-oauth2]] = OAuth2 WebFlux -Spring Security provides comprehensive OAuth 2.0 support. -This section discusses how to integrate OAuth 2.0 into your reactive application. +Spring Security provides OAuth2 and WebFlux integration for reactive applications. -[[oauth2-overview]] -== Overview - -Spring Security's OAuth 2.0 support consists of two primary feature sets: - -* <> -* <> - -[NOTE] -==== -<> is a very powerful OAuth2 Client feature that deserves its own section in the reference documentation. -However, it does not exist as a standalone feature and requires OAuth2 Client in order to function. -==== - -These feature sets cover the _resource server_ and _client_ roles defined in the https://tools.ietf.org/html/rfc6749#section-1.1[OAuth 2.0 Authorization Framework], while the _authorization server_ role is covered by https://docs.spring.io/spring-authorization-server/reference/index.html[Spring Authorization Server], which is a separate project built on xref:index.adoc[Spring Security]. - -The _resource server_ and _client_ roles in OAuth2 are typically represented by one or more server-side applications. -Additionally, the _authorization server_ role can be represented by one or more third parties (as is the case when centralizing identity management and/or authentication within an organization) *-or-* it can be represented by an application (as is the case with Spring Authorization Server). - -For example, a typical OAuth2-based microservices architecture might consist of a single user-facing client application, several backend resource servers providing REST APIs and a third party authorization server for managing users and authentication concerns. -It is also common to have a single application representing only one of these roles with the need to integrate with one or more third parties that are providing the other roles. - -Spring Security handles these scenarios and more. -The following sections cover the roles provided by Spring Security and contain examples for common scenarios. - -[[oauth2-resource-server]] -== OAuth2 Resource Server - -[NOTE] -==== -This section contains a summary of OAuth2 Resource Server features with examples. -See xref:reactive/oauth2/resource-server/index.adoc[OAuth 2.0 Resource Server] for complete reference documentation. -==== - -To get started, add the `spring-security-oauth2-resource-server` dependency to your project. -When using Spring Boot, add the following starter: - -.OAuth2 Client with Spring Boot -[tabs] -====== -Gradle:: -+ -[source,gradle,role="primary"] ----- -implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' ----- - -Maven:: -+ -[source,maven,role="secondary"] ----- - - org.springframework.boot - spring-boot-starter-oauth2-resource-server - ----- -====== - -[TIP] -==== -See xref:getting-spring-security.adoc[] for additional options when not using Spring Boot. -==== - -Consider the following use cases for OAuth2 Resource Server: - -* <> (authorization server provides JWT or opaque access token) -* <> (custom token) - -[[oauth2-resource-server-access-token]] -=== Protect Access with an OAuth2 Access Token - -It is very common to protect access to an API using OAuth2 access tokens. -In most cases, Spring Security requires only minimal configuration to secure an application with OAuth2. - -There are two types of `Bearer` tokens supported by Spring Security which each use a different component for validation: - -* <> uses a `ReactiveJwtDecoder` bean to validate signatures and decode tokens -* <> uses a `ReactiveOpaqueTokenIntrospector` bean to introspect tokens - -[[oauth2-resource-server-access-token-jwt]] -==== JWT Support - -The following example configures a `ReactiveJwtDecoder` bean using Spring Boot configuration properties: - -[source,yaml] ----- -spring: - security: - oauth2: - resourceserver: - jwt: - issuer-uri: https://my-auth-server.com ----- - -When using Spring Boot, this is all that is required. -The default arrangement provided by Spring Boot is equivalent to the following: - -.Configure Resource Server with JWTs -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt(Customizer.withDefaults()) - ); - return http.build(); - } - - @Bean - public ReactiveJwtDecoder jwtDecoder() { - return ReactiveJwtDecoders.fromIssuerLocation("https://my-auth-server.com"); - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.config.web.server.invoke - -@Configuration -@EnableWebFluxSecurity -class SecurityConfig { - - @Bean - fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - authorizeExchange { - authorize(anyExchange, authenticated) - } - oauth2ResourceServer { - jwt { } - } - } - } - - @Bean - fun jwtDecoder(): ReactiveJwtDecoder { - return ReactiveJwtDecoders.fromIssuerLocation("https://my-auth-server.com") - } - -} ----- -===== - -[[oauth2-resource-server-access-token-opaque]] -==== Opaque Token Support - -The following example configures an `OpaqueTokenIntrospector` bean using Spring Boot configuration properties: - -[source,yaml] ----- -spring: - security: - oauth2: - resourceserver: - opaquetoken: - introspection-uri: https://my-auth-server.com/oauth2/introspect - client-id: my-client-id - client-secret: my-client-secret ----- - -When using Spring Boot, this is all that is required. -The default arrangement provided by Spring Boot is equivalent to the following: - -.Configure Resource Server with Opaque Tokens -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .opaqueToken(Customizer.withDefaults()) - ); - return http.build(); - } - - @Bean - public ReactiveOpaqueTokenIntrospector opaqueTokenIntrospector() { - return new SpringReactiveOpaqueTokenIntrospector( - "https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret"); - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.config.web.server.invoke - -@Configuration -@EnableWebFluxSecurity -class SecurityConfig { - - @Bean - fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - authorizeExchange { - authorize(anyExchange, authenticated) - } - oauth2ResourceServer { - opaqueToken { } - } - } - } - - @Bean - fun opaqueTokenIntrospector(): ReactiveOpaqueTokenIntrospector { - return SpringReactiveOpaqueTokenIntrospector( - "https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret" - ) - } - -} ----- -===== - -[[oauth2-resource-server-custom-jwt]] -=== Protect Access with a custom JWT - -It is a fairly common goal to protect access to an API using JWTs, particularly when the frontend is developed as a single-page application. -The OAuth2 Resource Server support in Spring Security can be used for any type of `Bearer` token, including a custom JWT. - -All that is required to protect an API using JWTs is a `ReactiveJwtDecoder` bean, which is used to validate signatures and decode tokens. -Spring Security will automatically use the provided bean to configure protection within the `SecurityWebFilterChain`. - -The following example configures a `ReactiveJwtDecoder` bean using Spring Boot configuration properties: - -[source,yaml] ----- -spring: - security: - oauth2: - resourceserver: - jwt: - public-key-location: classpath:my-public-key.pub ----- - -[NOTE] -==== -You can provide the public key as a classpath resource (called `my-public-key.pub` in this example). -==== - -When using Spring Boot, this is all that is required. -The default arrangement provided by Spring Boot is equivalent to the following: - -.Configure Resource Server with Custom JWTs -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt(Customizer.withDefaults()) - ); - return http.build(); - } - - @Bean - public ReactiveJwtDecoder jwtDecoder() { - return NimbusReactiveJwtDecoder.withPublicKey(publicKey()).build(); - } - - private RSAPublicKey publicKey() { - // ... - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.config.web.server.invoke - -@Configuration -@EnableWebFluxSecurity -class SecurityConfig { - - @Bean - fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - authorizeExchange { - authorize(anyExchange, authenticated) - } - oauth2ResourceServer { - jwt { } - } - } - } - - @Bean - fun jwtDecoder(): ReactiveJwtDecoder { - return NimbusReactiveJwtDecoder.withPublicKey(publicKey()).build() - } - - private fun publicKey(): RSAPublicKey { - // ... - } - -} ----- -===== - -[NOTE] -==== -Spring Security does not provide an endpoint for minting tokens. -However, Spring Security does provide the `JwtEncoder` interface along with one implementation, which is `NimbusJwtEncoder`. -==== - -[[oauth2-client]] -== OAuth2 Client - -[NOTE] -==== -This section contains a summary of OAuth2 Client features with examples. -See xref:reactive/oauth2/client/index.adoc[OAuth 2.0 Client] and xref:reactive/oauth2/login/index.adoc[OAuth 2.0 Login] for complete reference documentation. -==== - -To get started, add the `spring-security-oauth2-client` dependency to your project. -When using Spring Boot, add the following starter: - -.OAuth2 Client with Spring Boot -[tabs] -====== -Gradle:: -+ -[source,gradle,role="primary"] ----- -implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' ----- - -Maven:: -+ -[source,maven,role="secondary"] ----- - - org.springframework.boot - spring-boot-starter-oauth2-client - ----- -====== - -[TIP] -==== -See xref:getting-spring-security.adoc[] for additional options when not using Spring Boot. -==== - -Consider the following use cases for OAuth2 Client: - -* <> -* <> -* <> (log users in _and_ access a third-party API) -* <> -* <> -* <> -* <> - -[[oauth2-client-log-users-in]] -=== Log Users In with OAuth2 - -It is very common to require users to log in via OAuth2. -https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect 1.0] provides a special token called the `id_token` which is designed to provide an OAuth2 Client with the ability to perform user identity verification and log users in. -In certain cases, OAuth2 can be used directly to log users in (as is the case with popular social login providers that do not implement OpenID Connect such as GitHub and Facebook). - -The following example configures the application to act as an OAuth2 Client capable of logging users in with OAuth2 or OpenID Connect: - -.Configure OAuth2 Login -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - http - // ... - .oauth2Login(Customizer.withDefaults()); - return http.build(); - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.config.web.server.invoke - -@Configuration -@EnableWebFluxSecurity -class SecurityConfig { - - @Bean - fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - // ... - oauth2Login { } - } - } - -} ----- -===== - -In addition to the above configuration, the application requires at least one `ClientRegistration` to be configured through the use of a `ReactiveClientRegistrationRepository` bean. -The following example configures an `InMemoryReactiveClientRegistrationRepository` bean using Spring Boot configuration properties: - -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: - my-oidc-client: - provider: my-oidc-provider - client-id: my-client-id - client-secret: my-client-secret - authorization-grant-type: authorization_code - scope: openid,profile - provider: - my-oidc-provider: - issuer-uri: https://my-oidc-provider.com ----- - -With the above configuration, the application now supports two additional endpoints: - -1. The login endpoint (e.g. `/oauth2/authorization/my-oidc-client`) is used to initiate login and perform a redirect to the third party authorization server. -2. The redirection endpoint (e.g. `/login/oauth2/code/my-oidc-client`) is used by the authorization server to redirect back to the client application, and will contain a `code` parameter used to obtain an `id_token` and/or `access_token` via the access token request. - -[NOTE] -==== -The presence of the `openid` scope in the above configuration indicates that OpenID Connect 1.0 should be used. -This instructs Spring Security to use OIDC-specific components (such as `OidcReactiveOAuth2UserService`) during request processing. -Without this scope, Spring Security will use OAuth2-specific components (such as `DefaultReactiveOAuth2UserService`) instead. -==== - -[[oauth2-client-access-protected-resources]] -=== Access Protected Resources - -Making requests to a third party API that is protected by OAuth2 is a core use case of OAuth2 Client. -This is accomplished by authorizing a client (represented by the `OAuth2AuthorizedClient` class in Spring Security) and accessing protected resources by placing a `Bearer` token in the `Authorization` header of an outbound request. - -The following example configures the application to act as an OAuth2 Client capable of requesting protected resources from a third party API: - -.Configure OAuth2 Client -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - http - // ... - .oauth2Client(Customizer.withDefaults()); - return http.build(); - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.config.web.server.invoke - -@Configuration -@EnableWebFluxSecurity -class SecurityConfig { - - @Bean - fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - // ... - oauth2Client { } - } - } - -} ----- -===== - -[NOTE] -==== -The above example does not provide a way to log users in. -You can use any other login mechanism (such as `formLogin()`). -See the <> for an example combining `oauth2Client()` with `oauth2Login()`. -==== - -In addition to the above configuration, the application requires at least one `ClientRegistration` to be configured through the use of a `ReactiveClientRegistrationRepository` bean. -The following example configures an `InMemoryReactiveClientRegistrationRepository` bean using Spring Boot configuration properties: - -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: - my-oauth2-client: - provider: my-auth-server - client-id: my-client-id - client-secret: my-client-secret - authorization-grant-type: authorization_code - scope: message.read,message.write - provider: - my-auth-server: - issuer-uri: https://my-auth-server.com ----- - -In addition to configuring Spring Security to support OAuth2 Client features, you will also need to decide how you will be accessing protected resources and configure your application accordingly. -Spring Security provides implementations of `ReactiveOAuth2AuthorizedClientManager` for obtaining access tokens that can be used to access protected resources. - -[TIP] -==== -Spring Security registers a default `ReactiveOAuth2AuthorizedClientManager` bean for you when one does not exist. -==== - -The easiest way to use a `ReactiveOAuth2AuthorizedClientManager` is via an `ExchangeFilterFunction` that intercepts requests through a `WebClient`. - -The following example uses the default `ReactiveOAuth2AuthorizedClientManager` to configure a `WebClient` capable of accessing protected resources by placing `Bearer` tokens in the `Authorization` header of each request: - -.Configure `WebClient` with `ExchangeFilterFunction` -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -public class WebClientConfig { - - @Bean - public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) { - ServerOAuth2AuthorizedClientExchangeFilterFunction filter = - new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); - return WebClient.builder() - .filter(filter) - .build(); - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Configuration -class WebClientConfig { - - @Bean - fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient { - val filter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager) - return WebClient.builder() - .filter(filter) - .build() - } - -} ----- -===== - -This configured `WebClient` can be used as in the following example: - -[[oauth2-client-accessing-protected-resources-example]] -.Use `WebClient` to Access Protected Resources -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId; - -@RestController -public class MessagesController { - - private final WebClient webClient; - - public MessagesController(WebClient webClient) { - this.webClient = webClient; - } - - @GetMapping("/messages") - public Mono>> messages() { - return this.webClient.get() - .uri("http://localhost:8090/messages") - .attributes(clientRegistrationId("my-oauth2-client")) - .retrieve() - .toEntityList(Message.class); - } - - public record Message(String message) { - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId - -@RestController -class MessagesController(private val webClient: WebClient) { - - @GetMapping("/messages") - fun messages(): Mono>> { - return webClient.get() - .uri("http://localhost:8090/messages") - .attributes(clientRegistrationId("my-oauth2-client")) - .retrieve() - .toEntityList() - } - - data class Message(val message: String) - -} ----- -===== - -[[oauth2-client-access-protected-resources-current-user]] -=== Access Protected Resources for the Current User - -When a user is logged in via OAuth2 or OpenID Connect, the authorization server may provide an access token that can be used directly to access protected resources. -This is convenient because it only requires a single `ClientRegistration` to be configured for both use cases simultaneously. - -[NOTE] -==== -This section combines <> and <> into a single configuration. -Other advanced scenarios exist, such as configuring one `ClientRegistration` for login and another for accessing protected resources. -All such scenarios would use the same basic configuration. -==== - -The following example configures the application to act as an OAuth2 Client capable of logging the user in _and_ requesting protected resources from a third party API: - -.Configure OAuth2 Login and OAuth2 Client -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - http - // ... - .oauth2Login(Customizer.withDefaults()) - .oauth2Client(Customizer.withDefaults()); - return http.build(); - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.config.web.server.invoke - -@Configuration -@EnableWebFluxSecurity -class SecurityConfig { - - @Bean - fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - // ... - oauth2Login { } - oauth2Client { } - } - } - -} ----- -===== - -In addition to the above configuration, the application requires at least one `ClientRegistration` to be configured through the use of a `ReactiveClientRegistrationRepository` bean. -The following example configures an `InMemoryReactiveClientRegistrationRepository` bean using Spring Boot configuration properties: - -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: - my-combined-client: - provider: my-auth-server - client-id: my-client-id - client-secret: my-client-secret - authorization-grant-type: authorization_code - scope: openid,profile,message.read,message.write - provider: - my-auth-server: - issuer-uri: https://my-auth-server.com ----- - -[NOTE] -==== -The main difference between the previous examples (<>, <>) and this one is what is configured via the `scope` property, which combines the standard scopes `openid` and `profile` with the custom scopes `message.read` and `message.write`. -==== - -In addition to configuring Spring Security to support OAuth2 Client features, you will also need to decide how you will be accessing protected resources and configure your application accordingly. -Spring Security provides implementations of `ReactiveOAuth2AuthorizedClientManager` for obtaining access tokens that can be used to access protected resources. - -[TIP] -==== -Spring Security registers a default `ReactiveOAuth2AuthorizedClientManager` bean for you when one does not exist. -==== - -The easiest way to use a `ReactiveOAuth2AuthorizedClientManager` is via an `ExchangeFilterFunction` that intercepts requests through a `WebClient`. - -The following example uses the default `ReactiveOAuth2AuthorizedClientManager` to configure a `WebClient` capable of accessing protected resources by placing `Bearer` tokens in the `Authorization` header of each request: - -.Configure `WebClient` with `ExchangeFilterFunction` -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -public class WebClientConfig { - - @Bean - public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) { - ServerOAuth2AuthorizedClientExchangeFilterFunction filter = - new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); - return WebClient.builder() - .filter(filter) - .build(); - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Configuration -class WebClientConfig { - - @Bean - fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient { - val filter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager) - return WebClient.builder() - .filter(filter) - .build() - } - -} ----- -===== - -This configured `WebClient` can be used as in the following example: - -[[oauth2-client-accessing-protected-resources-current-user-example]] -.Use `WebClient` to Access Protected Resources (Current User) -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@RestController -public class MessagesController { - - private final WebClient webClient; - - public MessagesController(WebClient webClient) { - this.webClient = webClient; - } - - @GetMapping("/messages") - public Mono>> messages() { - return this.webClient.get() - .uri("http://localhost:8090/messages") - .retrieve() - .toEntityList(Message.class); - } - - public record Message(String message) { - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@RestController -class MessagesController(private val webClient: WebClient) { - - @GetMapping("/messages") - fun messages(): Mono>> { - return webClient.get() - .uri("http://localhost:8090/messages") - .retrieve() - .toEntityList() - } - - data class Message(val message: String) - -} ----- -===== - -[NOTE] -==== -Unlike the <>, notice that we do not need to tell Spring Security about the `clientRegistrationId` we'd like to use. -This is because it can be derived from the currently logged in user. -==== - -[[oauth2-client-enable-extension-grant-type]] -=== Enable an Extension Grant Type - -A common use case involves enabling and/or configuring an extension grant type. -For example, Spring Security provides support for the `jwt-bearer` and `token-exchange` grant types, but does not enable them by default because they are not part of the core OAuth 2.0 specification. - -With Spring Security 6.3 and later, we can simply publish a bean for one or more `ReactiveOAuth2AuthorizedClientProvider` and they will be picked up automatically. -The following example simply enables the `jwt-bearer` grant type: - -.Enable `jwt-bearer` Grant Type -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -public class SecurityConfig { - - @Bean - public ReactiveOAuth2AuthorizedClientProvider jwtBearer() { - return new JwtBearerReactiveOAuth2AuthorizedClientProvider(); - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Configuration -class SecurityConfig { - - @Bean - fun jwtBearer(): ReactiveOAuth2AuthorizedClientProvider { - return JwtBearerReactiveOAuth2AuthorizedClientProvider() - } - -} ----- -===== - -A default `ReactiveOAuth2AuthorizedClientManager` will be published automatically by Spring Security when one is not already provided. - -[TIP] -==== -Any custom `OAuth2AuthorizedClientProvider` bean will also be picked up and applied to the provided `ReactiveOAuth2AuthorizedClientManager` after the default grant types. -==== - -In order to achieve the above configuration prior to Spring Security 6.3, we had to publish this bean ourselves and ensure we re-enabled default grant types as well. -To understand what is being configured behind the scenes, here's what the configuration might have looked like: - -.Enable `jwt-bearer` Grant Type (prior to 6.3) -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -public class SecurityConfig { - - @Bean - public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( - ReactiveClientRegistrationRepository clientRegistrationRepository, - ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { - - ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = - ReactiveOAuth2AuthorizedClientProviderBuilder.builder() - .authorizationCode() - .refreshToken() - .clientCredentials() - .password() - .provider(new JwtBearerReactiveOAuth2AuthorizedClientProvider()) - .build(); - - DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = - new DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); - - return authorizedClientManager; - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Configuration -class SecurityConfig { - - @Bean - fun authorizedClientManager( - clientRegistrationRepository: ReactiveClientRegistrationRepository, - authorizedClientRepository: ServerOAuth2AuthorizedClientRepository - ): ReactiveOAuth2AuthorizedClientManager { - val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder() - .authorizationCode() - .refreshToken() - .clientCredentials() - .password() - .provider(JwtBearerReactiveOAuth2AuthorizedClientProvider()) - .build() - - val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository - ) - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) - - return authorizedClientManager - } - -} ----- -===== - -[[oauth2-client-customize-existing-grant-type]] -=== Customize an Existing Grant Type - -The ability to <> by publishing a bean also provides the opportunity for customizing an existing grant type without the need to re-define the defaults. -For example, if we want to customize the clock skew of the `ReactiveOAuth2AuthorizedClientProvider` for the `client_credentials` grant, we can simply publish a bean like so: - -.Customize Client Credentials Grant Type -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -public class SecurityConfig { - - @Bean - public ReactiveOAuth2AuthorizedClientProvider clientCredentials() { - ClientCredentialsReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = - new ClientCredentialsReactiveOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setClockSkew(Duration.ofMinutes(5)); - - return authorizedClientProvider; - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Configuration -class SecurityConfig { - - @Bean - fun clientCredentials(): ReactiveOAuth2AuthorizedClientProvider { - val authorizedClientProvider = ClientCredentialsReactiveOAuth2AuthorizedClientProvider() - authorizedClientProvider.setClockSkew(Duration.ofMinutes(5)) - return authorizedClientProvider - } - -} ----- -===== - -[[oauth2-client-customize-request-parameters]] -=== Customize Token Request Parameters - -The need to customize request parameters when obtaining an access token is fairly common. -For example, let's say we want to add a custom `audience` parameter to the token request because the provider requires this parameter for the `authorization_code` grant. - -We can simply publish a bean of type `ReactiveOAuth2AccessTokenResponseClient` with the generic type `OAuth2AuthorizationCodeGrantRequest` and it will be used by Spring Security to configure OAuth2 Client components. - -The following example customizes token request parameters for the `authorization_code` grant: - -.Customize Token Request Parameters for Authorization Code Grant -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -public class SecurityConfig { - - @Bean - public ReactiveOAuth2AccessTokenResponseClient authorizationCodeAccessTokenResponseClient() { - WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient = - new WebClientReactiveAuthorizationCodeTokenResponseClient(); - accessTokenResponseClient.addParametersConverter(parametersConverter()); - - return accessTokenResponseClient; - } - - private static Converter> parametersConverter() { - return (grantRequest) -> { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set("audience", "xyz_value"); - - return parameters; - }; - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Configuration -class SecurityConfig { - - @Bean - fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { - val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient() - accessTokenResponseClient.addParametersConverter(parametersConverter()) - - return accessTokenResponseClient - } - - private fun parametersConverter(): Converter> { - return Converter> { grantRequest -> - LinkedMultiValueMap().also { parameters -> - parameters["audience"] = "xyz_value" - } - } - } - -} ----- -===== - -[TIP] -==== -Notice that we don't need to customize the `SecurityWebFilterChain` bean in this case, and can stick with the defaults. -If using Spring Boot with no additional customizations, we can actually omit the `SecurityWebFilterChain` bean entirely. -==== - -As you can see, providing the `ReactiveOAuth2AccessTokenResponseClient` as a bean is quite convenient. -When using the Spring Security DSL directly, we need to ensure that this customization is applied for both OAuth2 Login (if we are using this feature) and OAuth2 Client components. -To understand what is being configured behind the scenes, here's what the configuration would look like with the DSL: - -.Customize Token Request Parameters for Authorization Code Grant using the DSL -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient = - new WebClientReactiveAuthorizationCodeTokenResponseClient(); - accessTokenResponseClient.addParametersConverter(parametersConverter()); - - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .oauth2Login((oauth2Login) -> oauth2Login - .authenticationManager(new DelegatingReactiveAuthenticationManager( - new OidcAuthorizationCodeReactiveAuthenticationManager( - accessTokenResponseClient, new OidcReactiveOAuth2UserService() - ), - new OAuth2LoginReactiveAuthenticationManager( - accessTokenResponseClient, new DefaultReactiveOAuth2UserService() - ) - )) - ) - .oauth2Client((oauth2Client) -> oauth2Client - .authenticationManager(new OAuth2AuthorizationCodeReactiveAuthenticationManager( - accessTokenResponseClient - )) - ); - - return http.build(); - } - - private static Converter> parametersConverter() { - // ... - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.config.web.server.invoke - -@Configuration -@EnableWebFluxSecurity -class SecurityConfig { - - @Bean - fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient() - accessTokenResponseClient.addParametersConverter(parametersConverter()) - - return http { - authorizeExchange { - authorize(anyExchange, authenticated) - } - oauth2Login { - authenticationManager = DelegatingReactiveAuthenticationManager( - OidcAuthorizationCodeReactiveAuthenticationManager( - accessTokenResponseClient, OidcReactiveOAuth2UserService() - ), - OAuth2LoginReactiveAuthenticationManager( - accessTokenResponseClient, DefaultReactiveOAuth2UserService() - ) - ) - } - oauth2Client { - authenticationManager = OAuth2AuthorizationCodeReactiveAuthenticationManager( - accessTokenResponseClient - ) - } - } - } - - private fun parametersConverter(): Converter> { - // ... - } - -} ----- -===== - -For other grant types we can publish additional `ReactiveOAuth2AccessTokenResponseClient` beans to override the defaults. -For example, to customize token requests for the `client_credentials` grant we can publish the following bean: - -.Customize Token Request Parameters for Client Credentials Grant -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -public class SecurityConfig { - - @Bean - public ReactiveOAuth2AccessTokenResponseClient clientCredentialsAccessTokenResponseClient() { - WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient = - new WebClientReactiveClientCredentialsTokenResponseClient(); - accessTokenResponseClient.addParametersConverter(parametersConverter()); - - return accessTokenResponseClient; - } - - private static Converter> parametersConverter() { - // ... - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Configuration -class SecurityConfig { - - @Bean - fun clientCredentialsAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { - val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient() - accessTokenResponseClient.addParametersConverter(parametersConverter()) - - return accessTokenResponseClient - } - - private fun parametersConverter(): Converter> { - // ... - } - -} ----- -===== - -Spring Security automatically resolves the following generic types of `ReactiveOAuth2AccessTokenResponseClient` beans: - -* `OAuth2AuthorizationCodeGrantRequest` (see `WebClientReactiveAuthorizationCodeTokenResponseClient`) -* `OAuth2RefreshTokenGrantRequest` (see `WebClientReactiveRefreshTokenTokenResponseClient`) -* `OAuth2ClientCredentialsGrantRequest` (see `WebClientReactiveClientCredentialsTokenResponseClient`) -* `OAuth2PasswordGrantRequest` (see `WebClientReactivePasswordTokenResponseClient`) -* `JwtBearerGrantRequest` (see `WebClientReactiveJwtBearerTokenResponseClient`) -* `TokenExchangeGrantRequest` (see `WebClientReactiveTokenExchangeTokenResponseClient`) - -[TIP] -==== -Publishing a bean of type `ReactiveOAuth2AccessTokenResponseClient` will automatically enable the `jwt-bearer` grant type without the need to <>. -==== - -[TIP] -==== -Publishing a bean of type `ReactiveOAuth2AccessTokenResponseClient` will automatically enable the `token-exchange` grant type without the need to <>. -==== - -[[oauth2-client-customize-web-client]] -=== Customize the `WebClient` used by OAuth2 Client Components - -Another common use case is the need to customize the `WebClient` used when obtaining an access token. -We might need to do this to customize the underlying HTTP client library (via a custom `ClientHttpConnector`) to configure SSL settings or to apply proxy settings for a corporate network. - -With Spring Security 6.3 and later, we can simply publish beans of type `ReactiveOAuth2AccessTokenResponseClient` and Spring Security will configure and publish a `ReactiveOAuth2AuthorizedClientManager` bean for us. - -The following example customizes the `WebClient` for all of the supported grant types: - -.Customize `WebClient` for OAuth2 Client -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -public class SecurityConfig { - - @Bean - public ReactiveOAuth2AccessTokenResponseClient authorizationCodeAccessTokenResponseClient() { - WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient = - new WebClientReactiveAuthorizationCodeTokenResponseClient(); - accessTokenResponseClient.setWebClient(webClient()); - - return accessTokenResponseClient; - } - - @Bean - public ReactiveOAuth2AccessTokenResponseClient refreshTokenAccessTokenResponseClient() { - WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient = - new WebClientReactiveRefreshTokenTokenResponseClient(); - accessTokenResponseClient.setWebClient(webClient()); - - return accessTokenResponseClient; - } - - @Bean - public ReactiveOAuth2AccessTokenResponseClient clientCredentialsAccessTokenResponseClient() { - WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient = - new WebClientReactiveClientCredentialsTokenResponseClient(); - accessTokenResponseClient.setWebClient(webClient()); - - return accessTokenResponseClient; - } - - @Bean - public ReactiveOAuth2AccessTokenResponseClient passwordAccessTokenResponseClient() { - WebClientReactivePasswordTokenResponseClient accessTokenResponseClient = - new WebClientReactivePasswordTokenResponseClient(); - accessTokenResponseClient.setWebClient(webClient()); - - return accessTokenResponseClient; - } - - @Bean - public ReactiveOAuth2AccessTokenResponseClient jwtBearerAccessTokenResponseClient() { - WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient = - new WebClientReactiveJwtBearerTokenResponseClient(); - accessTokenResponseClient.setWebClient(webClient()); - - return accessTokenResponseClient; - } - - @Bean - public ReactiveOAuth2AccessTokenResponseClient tokenExchangeAccessTokenResponseClient() { - WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient = - new WebClientReactiveTokenExchangeTokenResponseClient(); - accessTokenResponseClient.setWebClient(webClient()); - - return accessTokenResponseClient; - } - - @Bean - public WebClient webClient() { - // ... - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Configuration -class SecurityConfig { - - @Bean - fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { - val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient() - accessTokenResponseClient.setWebClient(webClient()) - - return accessTokenResponseClient - } - - @Bean - fun refreshTokenAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { - val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient() - accessTokenResponseClient.setWebClient(webClient()) - - return accessTokenResponseClient - } - - @Bean - fun clientCredentialsAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { - val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient() - accessTokenResponseClient.setWebClient(webClient()) - - return accessTokenResponseClient - } - - @Bean - fun passwordAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { - val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient() - accessTokenResponseClient.setWebClient(webClient()) - - return accessTokenResponseClient - } - - @Bean - fun jwtBearerAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { - val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient() - accessTokenResponseClient.setWebClient(webClient()) - - return accessTokenResponseClient - } - - @Bean - fun tokenExchangeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { - val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient() - accessTokenResponseClient.setWebClient(webClient()) - - return accessTokenResponseClient - } - - @Bean - fun webClient(): WebClient { - // ... - } - -} ----- -===== - -A default `ReactiveOAuth2AuthorizedClientManager` will be published automatically by Spring Security when one is not already provided. - -[TIP] -==== -Notice that we don't need to customize the `SecurityWebFilterChain` bean in this case, and can stick with the defaults. -If using Spring Boot with no additional customizations, we can actually omit the `SecurityWebFilterChain` bean entirely. -==== - -Prior to Spring Security 6.3, we had to ensure this customization was applied to OAuth2 Client components ourselves. -While we could publish a bean of type `ReactiveOAuth2AccessTokenResponseClient` for the `authorization_code` grant, we had to publish a bean of type `ReactiveOAuth2AuthorizedClientManager` for other grant types. -To understand what is being configured behind the scenes, here's what the configuration might have looked like: - -.Customize `WebClient` for OAuth2 Client (prior to 6.3) -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -public class SecurityConfig { - - @Bean - public ReactiveOAuth2AccessTokenResponseClient authorizationCodeAccessTokenResponseClient() { - WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient = - new WebClientReactiveAuthorizationCodeTokenResponseClient(); - accessTokenResponseClient.setWebClient(webClient()); - - return accessTokenResponseClient; - } - - @Bean - public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( - ReactiveClientRegistrationRepository clientRegistrationRepository, - ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { - - WebClientReactiveRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient = - new WebClientReactiveRefreshTokenTokenResponseClient(); - refreshTokenAccessTokenResponseClient.setWebClient(webClient()); - - WebClientReactiveClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient = - new WebClientReactiveClientCredentialsTokenResponseClient(); - clientCredentialsAccessTokenResponseClient.setWebClient(webClient()); - - WebClientReactivePasswordTokenResponseClient passwordAccessTokenResponseClient = - new WebClientReactivePasswordTokenResponseClient(); - passwordAccessTokenResponseClient.setWebClient(webClient()); - - WebClientReactiveJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient = - new WebClientReactiveJwtBearerTokenResponseClient(); - jwtBearerAccessTokenResponseClient.setWebClient(webClient()); - - JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = - new JwtBearerReactiveOAuth2AuthorizedClientProvider(); - jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient); - - WebClientReactiveTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient = - new WebClientReactiveTokenExchangeTokenResponseClient(); - tokenExchangeAccessTokenResponseClient.setWebClient(webClient()); - - TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = - new TokenExchangeReactiveOAuth2AuthorizedClientProvider(); - tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient); - - ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = - ReactiveOAuth2AuthorizedClientProviderBuilder.builder() - .authorizationCode() - .refreshToken((refreshToken) -> refreshToken - .accessTokenResponseClient(refreshTokenAccessTokenResponseClient) - ) - .clientCredentials((clientCredentials) -> clientCredentials - .accessTokenResponseClient(clientCredentialsAccessTokenResponseClient) - ) - .password((password) -> password - .accessTokenResponseClient(passwordAccessTokenResponseClient) - ) - .provider(jwtBearerAuthorizedClientProvider) - .provider(tokenExchangeAuthorizedClientProvider) - .build(); - - DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = - new DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); - - return authorizedClientManager; - } - - @Bean - public WebClient webClient() { - // ... - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.config.web.server.invoke - -@Configuration -class SecurityConfig { - - @Bean - fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { - val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient() - accessTokenResponseClient.setWebClient(webClient()) - - return accessTokenResponseClient - } - - @Bean - fun authorizedClientManager( - clientRegistrationRepository: ReactiveClientRegistrationRepository?, - authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? - ): ReactiveOAuth2AuthorizedClientManager { - val refreshTokenAccessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient() - refreshTokenAccessTokenResponseClient.setWebClient(webClient()) - - val clientCredentialsAccessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient() - clientCredentialsAccessTokenResponseClient.setWebClient(webClient()) - - val passwordAccessTokenResponseClient = WebClientReactivePasswordTokenResponseClient() - passwordAccessTokenResponseClient.setWebClient(webClient()) - - val jwtBearerAccessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient() - jwtBearerAccessTokenResponseClient.setWebClient(webClient()) - - val jwtBearerAuthorizedClientProvider = JwtBearerReactiveOAuth2AuthorizedClientProvider() - jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient) - - val tokenExchangeAccessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient() - tokenExchangeAccessTokenResponseClient.setWebClient(webClient()) - - val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider() - tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient) - - val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() - .authorizationCode() - .refreshToken { refreshToken -> - refreshToken.accessTokenResponseClient(refreshTokenAccessTokenResponseClient) - } - .clientCredentials { clientCredentials -> - clientCredentials.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient) - } - .password { password -> - password.accessTokenResponseClient(passwordAccessTokenResponseClient) - } - .provider(jwtBearerAuthorizedClientProvider) - .provider(tokenExchangeAuthorizedClientProvider) - .build() - - val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository - ) - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) - - return authorizedClientManager - } - - @Bean - fun webClient(): WebClient { - // ... - } - -} ----- -===== - - -[[further-reading]] -== Further Reading - -This preceding sections introduced Spring Security's support for OAuth2 with examples for common scenarios. -You can read more about OAuth2 Client and Resource Server in the following sections of the reference documentation: - -* xref:reactive/oauth2/login/index.adoc[] -* xref:reactive/oauth2/client/index.adoc[] -* xref:reactive/oauth2/resource-server/index.adoc[] +* xref:reactive/oauth2/login/index.adoc[OAuth2 Log In] - Authenticating with an OAuth2 or OpenID Connect 1.0 Provider +* xref:reactive/oauth2/client/index.adoc[OAuth2 Client] - Making requests to an OAuth2 Resource Server +* xref:reactive/oauth2/resource-server/index.adoc[OAuth2 Resource Server] - Protecting a REST endpoint using OAuth2 diff --git a/docs/modules/ROOT/pages/reactive/oauth2/login/advanced.adoc b/docs/modules/ROOT/pages/reactive/oauth2/login/advanced.adoc index 4b6391eedae..319cba192b1 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/login/advanced.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/login/advanced.adoc @@ -472,13 +472,7 @@ public class OAuth2LoginSecurityConfig { // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities // 3) Create a copy of oidcUser but use the mappedAuthorities instead - ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails(); - String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName(); - if (StringUtils.hasText(userNameAttributeName)) { - oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo(), userNameAttributeName); - } else { - oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo()); - } + oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo()); return Mono.just(oidcUser); }); @@ -519,13 +513,7 @@ class OAuth2LoginSecurityConfig { // 1) Fetch the authority information from the protected resource using accessToken // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities // 3) Create a copy of oidcUser but use the mappedAuthorities instead - val providerDetails = userRequest.getClientRegistration().getProviderDetails() - val userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName() - val mappedOidcUser = if (StringUtils.hasText(userNameAttributeName)) { - DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo, userNameAttributeName) - } else { - DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo) - } + val mappedOidcUser = DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo) Mono.just(mappedOidcUser) } diff --git a/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc b/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc index 8ea1642c1b8..2eb15442c69 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc @@ -1,9 +1,9 @@ = Core Configuration [[webflux-oauth2-login-sample]] -== Spring Boot Sample +== Spring Boot 2.x Sample -Spring Boot brings full auto-configuration capabilities for OAuth 2.0 Login. +Spring Boot 2.x brings full auto-configuration capabilities for OAuth 2.0 Login. This section shows how to configure the {gh-samples-url}/boot/oauth2login-webflux[*OAuth 2.0 Login WebFlux sample*] by using _Google_ as the _Authentication Provider_ and covers the following topics: @@ -80,7 +80,7 @@ spring: [[webflux-oauth2-login-sample-start]] === Boot the Application -Launch the Spring Boot sample and go to `http://localhost:8080`. +Launch the Spring Boot 2.x sample and go to `http://localhost:8080`. You are then redirected to the default _auto-generated_ login page, which displays a link for Google. Click on the Google link, and you are then redirected to Google for authentication. @@ -93,12 +93,12 @@ At this point, the OAuth Client retrieves your email address and basic profile i [[oauth2login-boot-property-mappings]] -== Spring Boot Property Mappings +== Spring Boot 2.x Property Mappings -The following table outlines the mapping of the Spring Boot OAuth Client properties to the xref:reactive/oauth2/client/core.adoc#oauth2Client-client-registration[ClientRegistration] properties. +The following table outlines the mapping of the Spring Boot 2.x OAuth Client properties to the xref:reactive/oauth2/client/core.adoc#oauth2Client-client-registration[ClientRegistration] properties. |=== -|Spring Boot |ClientRegistration +|Spring Boot 2.x |ClientRegistration |`spring.security.oauth2.client.registration._[registrationId]_` |`registrationId` @@ -204,7 +204,7 @@ There are some OAuth 2.0 Providers that support multi-tenancy, which results in For example, an OAuth Client registered with Okta is assigned to a specific sub-domain and have their own protocol endpoints. -For these cases, Spring Boot provides the following base property for configuring custom provider properties: `spring.security.oauth2.client.provider._[providerId]_`. +For these cases, Spring Boot 2.x provides the following base property for configuring custom provider properties: `spring.security.oauth2.client.provider._[providerId]_`. The following listing shows an example: @@ -231,9 +231,9 @@ spring: [[webflux-oauth2-login-override-boot-autoconfig]] -== Overriding Spring Boot Auto-configuration +== Overriding Spring Boot 2.x Auto-configuration -The Spring Boot auto-configuration class for OAuth Client support is `ReactiveOAuth2ClientAutoConfiguration`. +The Spring Boot 2.x auto-configuration class for OAuth Client support is `ReactiveOAuth2ClientAutoConfiguration`. It performs the following tasks: @@ -469,9 +469,9 @@ class OAuth2LoginConfig { [[webflux-oauth2-login-javaconfig-wo-boot]] -== Java Configuration without Spring Boot +== Java Configuration without Spring Boot 2.x -If you are not able to use Spring Boot and would like to configure one of the pre-defined providers in `CommonOAuth2Provider` (for example, Google), apply the following configuration: +If you are not able to use Spring Boot 2.x and would like to configure one of the pre-defined providers in `CommonOAuth2Provider` (for example, Google), apply the following configuration: .OAuth2 Login Configuration [tabs] diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index 59e48e0986d..86e85cd8bad 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -1331,12 +1331,6 @@ Reference to an `OpaqueTokenAuthenticationConverter`. Responsible for converting == The container element for relying party(ies) registered (xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[ClientRegistration]) with a SAML 2.0 Identity Provider. -[[nsa-relying-party-registrations-attributes]] -=== Attributes - -[[nsa-relying-party-registrations-id]] -* **id** -The ID that uniquely identifies the `RelyingPartyRegistrationRepository`. [[nsa-relying-party-registrations-children]] === Child Elements of diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/index.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/index.adoc index 70127759763..4460fef52ae 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/index.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/index.adoc @@ -6,4 +6,4 @@ This appendix provides a reference to the elements available in the security nam If you haven't used the namespace before, please read the xref:servlet/configuration/xml-namespace.adoc#ns-config[introductory chapter] on namespace configuration, as this is intended as a supplement to the information there. Using a good quality XML editor while editing a configuration based on the schema is recommended as this will provide contextual information on which elements and attributes are available as well as comments explaining their purpose. The namespace is written in https://relaxng.org/[RELAX NG] Compact format and later converted into an XSD schema. -If you are familiar with this format, you may wish to examine the https://raw.githubusercontent.com/spring-projects/spring-security/main/config/src/main/resources/org/springframework/security/config/spring-security-6.3.rnc[schema file] directly. +If you are familiar with this format, you may wish to examine the https://raw.githubusercontent.com/spring-projects/spring-security/main/config/src/main/resources/org/springframework/security/config/spring-security-6.2.rnc[schema file] directly. diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/caching.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/caching.adoc deleted file mode 100644 index 9c0d37771e4..00000000000 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/caching.adoc +++ /dev/null @@ -1,156 +0,0 @@ -[[servlet-authentication-caching-user-details]] -= Caching `UserDetails` - -Spring Security provides support for caching `UserDetails` with <>. -Alternatively, you can use Spring Framework's <> annotation. -In either case, you will need to <> in order to validate passwords retrieved from the cache. - -[[servlet-authentication-caching-user-details-service]] -== `CachingUserDetailsService` - -Spring Security's `CachingUserDetailsService` implements xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[UserDetailsService] to provide support for caching `UserDetails`. -`CachingUserDetailsService` provides caching support for `UserDetails` by delegating to the provided `UserDetailsService`. -The result is then stored in a `UserCache` to reduce computation in subsequent calls. - -The following example simply defines a `@Bean` that encapsulates a concrete implementation of `UserDetailsService` and a `UserCache` for caching the `UserDetails`: - -.Provide a `CachingUserDetailsService` `@Bean` -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -public CachingUserDetailsService cachingUserDetailsService(UserCache userCache) { - UserDetailsService delegate = ...; - CachingUserDetailsService service = new CachingUserDetailsService(delegate); - service.setUserCache(userCache); - return service; -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun cachingUserDetailsService(userCache: UserCache): CachingUserDetailsService { - val delegate: UserDetailsService = ... - val service = CachingUserDetailsService(delegate) - service.userCache = userCache - return service -} ----- -====== - -[[servlet-authentication-caching-user-details-cacheable]] -== `@Cacheable` - -An alternative approach would be to use Spring Framework's {spring-framework-reference-url}integration.html#cache-annotations-cacheable[`@Cacheable`] in your `UserDetailsService` implementation to cache `UserDetails` by `username`. -The benefit to this approach is simpler configuration, especially if you are already using caching elsewhere in your application. - -The following example assumes caching is already configured, and annotates the `loadUserByUsername` with `@Cacheable`: - -.`UserDetailsService` annotated with `@Cacheable` -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Service -public class MyCustomUserDetailsImplementation implements UserDetailsService { - - @Override - @Cacheable - public UserDetails loadUserByUsername(String username) { - // some logic here to get the actual user details - return userDetails; - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Service -class MyCustomUserDetailsImplementation : UserDetailsService { - - @Cacheable - override fun loadUserByUsername(username: String): UserDetails { - // some logic here to get the actual user details - return userDetails - } -} ----- -====== - -[[servlet-authentication-caching-user-details-credential-erasure]] -== Disable Credential Erasure - -Whether you use <> or <>, you will need to disable xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager-erasing-credentials[credential erasure] so that the `UserDetails` will contain a `password` to be validated when retrieved from the cache. -The following example disables credential erasure for the global `AuthenticationManager` by configuring the `AuthenticationManagerBuilder` provided by Spring Security: - -.Disable credential erasure for the global `AuthenticationManager` -[tabs] -===== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -@EnableWebSecurity -public class SecurityConfig { - - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // ... - return http.build(); - } - - @Bean - public UserDetailsService userDetailsService() { - // Return a UserDetailsService that caches users - // ... - } - - @Autowired - public void configure(AuthenticationManagerBuilder builder) { - builder.eraseCredentials(false); - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.config.annotation.web.invoke - -@Configuration -@EnableWebSecurity -class SecurityConfig { - - @Bean - fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - // ... - return http.build() - } - - @Bean - fun userDetailsService(): UserDetailsService { - // Return a UserDetailsService that caches users - // ... - } - - @Autowired - fun configure(builder: AuthenticationManagerBuilder) { - builder.eraseCredentials(false) - } - -} ----- -===== diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc index af6ed15daa5..0e7b89c9509 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc @@ -2,7 +2,7 @@ = UserDetailsService {security-api-url}org/springframework/security/core/userdetails/UserDetailsService.html[`UserDetailsService`] is used by xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] for retrieving a username, a password, and other attributes for authenticating with a username and password. -Spring Security provides xref:servlet/authentication/passwords/in-memory.adoc#servlet-authentication-inmemory[in-memory], xref:servlet/authentication/passwords/jdbc.adoc#servlet-authentication-jdbc[JDBC], and xref:servlet/authentication/passwords/caching.adoc#servlet-authentication-caching-user-details[caching] implementations of `UserDetailsService`. +Spring Security provides xref:servlet/authentication/passwords/in-memory.adoc#servlet-authentication-inmemory[in-memory] and xref:servlet/authentication/passwords/jdbc.adoc#servlet-authentication-jdbc[JDBC] implementations of `UserDetailsService`. You can define custom authentication by exposing a custom `UserDetailsService` as a bean. For example, the following listing customizes authentication, assuming that `CustomUserDetailsService` implements `UserDetailsService`: diff --git a/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc b/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc index 256d6490151..35e797f6a8c 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc @@ -50,7 +50,7 @@ If you have more than one in your application context, you need to specify which [[remember-me-persistent-token]] == Persistent Token Approach -This approach is based on the article titled http://jaspan.com/improved_persistent_login_cookie_best_practice[http://jaspan.com/improved_persistent_login_cookie_best_practice], with some minor modifications. (Essentially, the username is not included in the cookie, to prevent exposing a valid login name unnecessarily. +This approach is based on the article titled http://jaspan.com/improved_persistent_login_cookie_best_practice[http://jaspan.com/improved_persistent_login_cookie_best_practice], with some minor modifications. (Essentially, the username is not included in the cookie, to prevent exposing a valid login name unecessarily. There is a discussion on this in the comments section of this article.) To use the this approach with namespace configuration, supply a datasource reference: diff --git a/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc b/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc index 6e65ea05531..d162ff5b81b 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc @@ -253,11 +253,11 @@ Java:: ---- @Bean static RoleHierarchy roleHierarchy() { - return RoleHierarchyImpl.withDefaultRolePrefix() - .role("ADMIN").implies("STAFF") - .role("STAFF").implies("USER") - .role("USER").implies("GUEST") - .build(); + RoleHierarchyImpl hierarchy = new RoleHierarchyImpl(); + hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" + + "ROLE_STAFF > ROLE_USER\n" + + "ROLE_USER > ROLE_GUEST"); + return hierarchy; } // and, if using method security also add @@ -274,14 +274,14 @@ Xml:: [source,java,role="secondary"] ---- - + class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl"> + ROLE_ADMIN > ROLE_STAFF ROLE_STAFF > ROLE_USER ROLE_USER > ROLE_GUEST - + diff --git a/docs/modules/ROOT/pages/servlet/authorization/index.adoc b/docs/modules/ROOT/pages/servlet/authorization/index.adoc index f38085253f8..1a0579336bd 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/index.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/index.adoc @@ -8,7 +8,6 @@ The advanced authorization capabilities within Spring Security represent one of Irrespective of how you choose to authenticate (whether using a Spring Security-provided mechanism and provider or integrating with a container or other non-Spring Security authentication authority), the authorization services can be used within your application in a consistent and simple way. You should consider attaching authorization rules to xref:servlet/authorization/authorize-http-requests.adoc[request URIs] and xref:servlet/authorization/method-security.adoc[methods] to begin. -In either case, you can listen and react to xref:servlet/authorization/events.adoc[authorization events] that each authorization check publishes. Below there is also wealth of detail about xref:servlet/authorization/architecture.adoc[how Spring Security authorization works] and how, having established a basic model, it can be fine-tuned. diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index 390b376ea99..6e338e93c3a 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -44,7 +44,6 @@ Consider learning about the following use cases: * Understanding <> and reasons to use it * Comparing <> * Authorizing methods with <> and <> -* Providing <> * Filtering methods with <> and <> * Authorizing methods with <> * Authorizing methods with <> @@ -161,12 +160,12 @@ For example, if needed, you can disable the Spring Security defaults and <<_enab The method interceptors are as follows: -* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#preAuthorize`], which in turn uses {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager`] -* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#postAuthorize`], which in turn uses {security-api-url}org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.html[`PostAuthorizeAuthorizationManager`] +* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthenticationManagerBeforeMethodInterceptor#preAuthorize`], which in turn uses {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager`] +* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.html[`AuthenticationManagerAfterMethodInterceptor#postAuthorize`], which in turn uses {security-api-url}org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.html[`PostAuthorizeAuthorizationManager`] * For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.html[`PreFilterAuthorizationMethodInterceptor`] * For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.html[`PostFilterAuthorizationMethodInterceptor`] -* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#secured`], which in turn uses {security-api-url}org/springframework/security/authorization/method/SecuredAuthorizationManager.html[`SecuredAuthorizationManager`] -* For JSR-250 annotations, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#jsr250`], which in turn uses {security-api-url}org/springframework/security/authorization/method/Jsr250AuthorizationManager.html[`Jsr250AuthorizationManager`] +* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthenticationManagerBeforeMethodInterceptor#secured`], which in turn uses {security-api-url}org/springframework/security/authorization/method/SecuredAuthorizationManager.html[`SecuredAuthorizationManager`] +* For JSR-250 annotations, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthenticationManagerBeforeMethodInterceptor#jsr250`], which in turn uses {security-api-url}org/springframework/security/authorization/method/Jsr250AuthorizationManager.html[`Jsr250AuthorizationManager`] Generally speaking, you can consider the following listing as representative of what interceptors Spring Security publishes when you add `@EnableMethodSecurity`: @@ -234,7 +233,7 @@ Java:: ---- @Bean static RoleHierarchy roleHierarchy() { - return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > permission:read"); + return new RoleHierarchyImpl("ROLE_ADMIN > permission:read"); } ---- @@ -245,7 +244,7 @@ Kotlin:: companion object { @Bean fun roleHierarchy(): RoleHierarchy { - return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > permission:read") + return RoleHierarchyImpl("ROLE_ADMIN > permission:read") } } ---- @@ -254,8 +253,7 @@ Xml:: + [source,xml,role="secondary"] ---- - + ---- @@ -901,157 +899,6 @@ open class BankService { This results in more readable method definitions. -==== Templating Meta-Annotation Expressions - -You can also opt into using meta-annotation templates, which allow for much more powerful annotation definitions. - -First, publish the following bean: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -static PrePostTemplateDefaults prePostTemplateDefaults() { - return new PrePostTemplateDefaults(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -companion object { - @Bean - fun prePostTemplateDefaults(): PrePostTemplateDefaults { - return PrePostTemplateDefaults() - } -} ----- -====== - -Now instead of `@IsAdmin`, you can create something more powerful like `@HasRole` like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Target({ ElementType.METHOD, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@PreAuthorize("hasRole('{value}')") -public @interface HasRole { - String value(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Target(ElementType.METHOD, ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@PreAuthorize("hasRole('{value}')") -annotation class IsAdmin(val value: String) ----- -====== - -And the result is that on your secured methods you can now do the following instead: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Component -public class BankService { - @HasRole("ADMIN") - public Account readAccount(Long id) { - // ... is only returned if the `Account` belongs to the logged in user - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Component -open class BankService { - @HasRole("ADMIN") - fun readAccount(val id: Long): Account { - // ... is only returned if the `Account` belongs to the logged in user - } -} ----- -====== - -Note that this works with method variables and all annotation types, too, though you will want to be careful to correctly take care of quotation marks so the resulting SpEL expression is correct. - -For example, consider the following `@HasAnyRole` annotation: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Target({ ElementType.METHOD, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@PreAuthorize("hasAnyRole({roles})") -public @interface HasAnyRole { - String[] roles(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Target(ElementType.METHOD, ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@PreAuthorize("hasAnyRole({roles})") -annotation class HasAnyRole(val roles: Array) ----- -====== - -In that case, you'll notice that you should not use the quotation marks in the expression, but instead in the parameter value like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Component -public class BankService { - @HasAnyRole(roles = { "'USER'", "'ADMIN'" }) - public Account readAccount(Long id) { - // ... is only returned if the `Account` belongs to the logged in user - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Component -open class BankService { - @HasAnyRole(roles = arrayOf("'USER'", "'ADMIN'")) - fun readAccount(val id: Long): Account { - // ... is only returned if the `Account` belongs to the logged in user - } -} ----- -====== - -so that, once replaced, the expression becomes `@PreAuthorize("hasAnyRole('USER', 'ADMIN')")`. - [[enable-annotation]] === Enabling Certain Annotations @@ -1215,45 +1062,6 @@ Spring Security will invoke the given method on that bean for each method invoca What's nice about this is all your authorization logic is in a separate class that can be independently unit tested and verified for correctness. It also has access to the full Java language. -[TIP] -In addition to returning a `Boolean`, you can also return `null` to indicate that the code abstains from making a decision. - -If you want to include more information about the nature of the decision, you can instead return a custom `AuthorizationDecision` like this: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Component("authz") -public class AuthorizationLogic { - public AuthorizationDecision decide(MethodSecurityExpressionOperations operations) { - // ... authorization logic - return new MyAuthorizationDecision(false, details); - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Component("authz") -open class AuthorizationLogic { - fun decide(val operations: MethodSecurityExpressionOperations): AuthorizationDecision { - // ... authorization logic - return MyAuthorizationDecision(false, details) - } -} ----- -====== - -Or throw a custom `AuthorizationDeniedException` instance. -Note, though, that returning an object is preferred as this doesn't incur the expense of generating a stacktrace. - -Then, you can access the custom details when you <>. - [[custom-authorization-managers]] === Using a Custom Authorization Manager @@ -1657,11 +1465,6 @@ Xml:: <4> This method may only be invoked by ``Princpal``s with an `aud` claim equal to "my-audience" <5> This method may only be invoked if the bean ``authz``'s `check` method returns `true` -[TIP] -==== -You can use a bean like `authz` above to <<_using_a_custom_bean_in_spel, add programmatic authorization>>. -==== - [[using_method_parameters]] === Using Method Parameters @@ -1747,971 +1550,6 @@ This works on both classes and interfaces. This does not work for interfaces, since they do not have debug information about the parameter names. For interfaces, either annotations or the `-parameters` approach must be used. -[[authorize-object]] -== Authorizing Arbitrary Objects - -Spring Security also supports wrapping any object that is annotated its method security annotations. - -The simplest way to achieve this is to mark any method that returns the object you wish to authorize with the `@AuthorizeReturnObject` annotation. - -For example, consider the following `User` class: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -public class User { - private String name; - private String email; - - public User(String name, String email) { - this.name = name; - this.email = email; - } - - public String getName() { - return this.name; - } - - @PreAuthorize("hasAuthority('user:read')") - public String getEmail() { - return this.email; - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -class User (val name:String, @get:PreAuthorize("hasAuthority('user:read')") val email:String) ----- -====== - -Given an interface like this one: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -public class UserRepository { - @AuthorizeReturnObject - Optional findByName(String name) { - // ... - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -class UserRepository { - @AuthorizeReturnObject - fun findByName(name:String?): Optional? { - // ... - } -} ----- -====== - -Then any `User` that is returned from `findById` will be secured like other Spring Security-protected components: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Autowired -UserRepository users; - -@Test -void getEmailWhenProxiedThenAuthorizes() { - Optional securedUser = users.findByName("name"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> securedUser.get().getEmail()); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- - -import jdk.incubator.vector.VectorOperators.Test -import java.nio.file.AccessDeniedException -import java.util.* - -@Autowired -var users:UserRepository? = null - -@Test -fun getEmailWhenProxiedThenAuthorizes() { - val securedUser: Optional = users.findByName("name") - assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy{securedUser.get().getEmail()} -} ----- -====== - -=== Using `@AuthorizeReturnObject` at the class level - -`@AuthorizeReturnObject` can be placed at the class level. Note, though, that this means Spring Security will attempt to proxy any return object, including ``String``, ``Integer`` and other types. -This is often not what you want to do. - -If you want to use `@AuthorizeReturnObject` on a class or interface whose methods return value types, like `int`, `String`, `Double` or collections of those types, then you should also publish the appropriate `AuthorizationAdvisorProxyFactory.TargetVisitor` as follows: - - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -static Customizer skipValueTypes() { - return (factory) -> factory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes()); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun skipValueTypes() = Customizer { - it.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes()) -} ----- -====== - -[TIP] -==== -You can set your own `AuthorizationAdvisorProxyFactory.TargetVisitor` to customize the proxying for any set of types -==== - -=== Programmatically Proxying - -You can also programmatically proxy a given object. - -To achieve this, you can autowire the provided `AuthorizationProxyFactory` instance, which is based on which method security interceptors you have configured. -If you are using `@EnableMethodSecurity`, then this means that it will by default have the interceptors for `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter`. - - -You can proxy an instance of user in the following way: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Autowired -AuthorizationProxyFactory proxyFactory; - -@Test -void getEmailWhenProxiedThenAuthorizes() { - User user = new User("name", "email"); - assertThat(user.getEmail()).isNotNull(); - User securedUser = proxyFactory.proxy(user); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Autowired -var proxyFactory:AuthorizationProxyFactory? = null - -@Test -fun getEmailWhenProxiedThenAuthorizes() { - val user: User = User("name", "email") - assertThat(user.getEmail()).isNotNull() - val securedUser: User = proxyFactory.proxy(user) - assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy(securedUser::getEmail) -} ----- -====== - -=== Manual Construction - -You can also define your own instance if you need something different from the Spring Security default. - -For example, if you define an `AuthorizationProxyFactory` instance like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor; -import static org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize; -// ... - -AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults(); -// and if needing to skip value types -proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes()); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor; -import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize - -// ... - -val proxyFactory: AuthorizationProxyFactory = AuthorizationProxyFactory(preAuthorize()) -// and if needing to skip value types -proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes()) ----- -====== - -Then you can wrap any instance of `User` as follows: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Test -void getEmailWhenProxiedThenAuthorizes() { - AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults(); - User user = new User("name", "email"); - assertThat(user.getEmail()).isNotNull(); - User securedUser = proxyFactory.proxy(user); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Test -fun getEmailWhenProxiedThenAuthorizes() { - val proxyFactory: AuthorizationProxyFactory = AuthorizationAdvisorProxyFactory.withDefaults() - val user: User = User("name", "email") - assertThat(user.getEmail()).isNotNull() - val securedUser: User = proxyFactory.proxy(user) - assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy(securedUser::getEmail) -} ----- -====== - -[NOTE] -==== -This feature does not yet support Spring AOT -==== - -=== Proxying Collections - -`AuthorizationProxyFactory` supports Java collections, streams, arrays, optionals, and iterators by proxying the element type and maps by proxying the value type. - -This means that when proxying a `List` of objects, the following also works: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Test -void getEmailWhenProxiedThenAuthorizes() { - AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults(); - List users = List.of(ada, albert, marie); - List securedUsers = proxyFactory.proxy(users); - securedUsers.forEach((securedUser) -> - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail)); -} ----- -====== - -=== Proxying Classes - -In limited circumstances, it may be valuable to proxy a `Class` itself, and `AuthorizationProxyFactory` also supports this. -This is roughly the equivalent of calling `ProxyFactory#getProxyClass` in Spring Framework's support for creating proxies. - -One place where this is handy is when you need to construct the proxy class ahead-of-time, like with Spring AOT. - -=== Support for All Method Security Annotations - -`AuthorizationProxyFactory` supports whichever method security annotations are enabled in your application. -It is based off of whatever `AuthorizationAdvisor` classes are published as a bean. - -Since `@EnableMethodSecurity` publishes `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` advisors by default, you will typically need to do nothing to activate the ability. - -[NOTE] -==== -SpEL expressions that use `returnObject` or `filterObject` sit behind the proxy and so have full access to the object. -==== - -[#custom_advice] -=== Custom Advice - -If you have security advice that you also want applied, you can publish your own `AuthorizationAdvisor` like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@EnableMethodSecurity -class SecurityConfig { - @Bean - static AuthorizationAdvisor myAuthorizationAdvisor() { - return new AuthorizationAdvisor(); - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@EnableMethodSecurity -internal class SecurityConfig { - @Bean - fun myAuthorizationAdvisor(): AuthorizationAdvisor { - return AuthorizationAdvisor() - } -] ----- -====== - -And Spring Security will add that advisor into the set of advice that `AuthorizationProxyFactory` adds when proxying an object. - -=== Working with Jackson - -One powerful use of this feature is to return a secured value from a controller like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@RestController -public class UserController { - @Autowired - AuthorizationProxyFactory proxyFactory; - - @GetMapping - User currentUser(@AuthenticationPrincipal User user) { - return this.proxyFactory.proxy(user); - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@RestController -class UserController { - @Autowired - var proxyFactory: AuthorizationProxyFactory? = null - - @GetMapping - fun currentUser(@AuthenticationPrincipal user:User?): User { - return proxyFactory.proxy(user) - } -} ----- -====== - -If you are using Jackson, though, this may result in a serialization error like the following: - -[source,bash] -==== -com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle -==== - -This is due to how Jackson works with CGLIB proxies. -To address this, add the following annotation to the top of the `User` class: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@JsonSerialize(as = User.class) -public class User { - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@JsonSerialize(`as` = User::class) -class User ----- -====== - -Finally, you will need to publish a <> to catch the `AccessDeniedException` thrown for each field, which you can do like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Component -public class AccessDeniedExceptionInterceptor implements AuthorizationAdvisor { - private final AuthorizationAdvisor advisor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize(); - - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - try { - return invocation.proceed(); - } catch (AccessDeniedException ex) { - return null; - } - } - - @Override - public Pointcut getPointcut() { - return this.advisor.getPointcut(); - } - - @Override - public Advice getAdvice() { - return this; - } - - @Override - public int getOrder() { - return this.advisor.getOrder() - 1; - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Component -class AccessDeniedExceptionInterceptor: AuthorizationAdvisor { - var advisor: AuthorizationAdvisor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize() - - @Throws(Throwable::class) - fun invoke(invocation: MethodInvocation): Any? { - return try { - invocation.proceed() - } catch (ex:AccessDeniedException) { - null - } - } - - val pointcut: Pointcut - get() = advisor.getPointcut() - - val advice: Advice - get() = this - - val order: Int - get() = advisor.getOrder() - 1 -} ----- -====== - -Then, you'll see a different JSON serialization based on the authorization level of the user. -If they don't have the `user:read` authority, then they'll see: - -[source,json] ----- -{ - "name" : "name", - "email" : null -} ----- - -And if they do have that authority, they'll see: - -[source,json] ----- -{ - "name" : "name", - "email" : "email" -} ----- - -[TIP] -==== -You can also add the Spring Boot property `spring.jackson.default-property-inclusion=non_null` to exclude the null value, if you also don't want to reveal the JSON key to an unauthorized user. -==== - -[[fallback-values-authorization-denied]] -== Providing Fallback Values When Authorization is Denied - -There are some scenarios where you may not wish to throw an `AuthorizationDeniedException` when a method is invoked without the required permissions. -Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases where authorization denied happened before invoking the method. - -Spring Security provides support for handling authorization denied on method invocation by using the {security-api-url}org/springframework/security/authorization/method/HandleAuthorizationDenied.html[`@HandleAuthorizationDenied`]. -The handler works for denied authorizations that happened in the <> as well as {security-api-url}org/springframework/security/authorization/AuthorizationDeniedException.html[`AuthorizationDeniedException`] thrown from the method invocation itself. - -Let's consider the example from the <>, but instead of creating the `AccessDeniedExceptionInterceptor` to transform an `AccessDeniedException` to a `null` return value, we will use the `handlerClass` attribute from `@HandleAuthorizationDenied`: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -public class NullMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1> - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - return null; - } - -} - -@Configuration -@EnableMethodSecurity -public class SecurityConfig { - - @Bean <2> - public NullMethodAuthorizationDeniedHandler nullMethodAuthorizationDeniedHandler() { - return new NullMethodAuthorizationDeniedHandler(); - } - -} - -public class User { - // ... - - @PreAuthorize(value = "hasAuthority('user:read')") - @HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler.class) - public String getEmail() { - return this.email; - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -class NullMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler { <1> - - override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any { - return null - } - -} - -@Configuration -@EnableMethodSecurity -class SecurityConfig { - - @Bean <2> - fun nullMethodAuthorizationDeniedHandler(): NullMethodAuthorizationDeniedHandler { - return MaskMethodAuthorizationDeniedHandler() - } - -} - -class User (val name:String, @PreAuthorize(value = "hasAuthority('user:read')") @HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler::class) val email:String) <3> ----- -====== - -<1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a `null` value -<2> Register the `NullMethodAuthorizationDeniedHandler` as a bean -<3> Annotate the method with `@HandleAuthorizationDenied` and pass the `NullMethodAuthorizationDeniedHandler` to the `handlerClass` attribute - -And then you can verify that a `null` value is returned instead of the `AccessDeniedException`: - -[TIP] -==== -You can also annotate your class with `@Component` instead of creating a `@Bean` method -==== - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Autowired -UserRepository users; - -@Test -void getEmailWhenProxiedThenNullEmail() { - Optional securedUser = users.findByName("name"); - assertThat(securedUser.get().getEmail()).isNull(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Autowired -var users:UserRepository? = null - -@Test -fun getEmailWhenProxiedThenNullEmail() { - val securedUser: Optional = users.findByName("name") - assertThat(securedUser.get().getEmail()).isNull() -} ----- -====== - -=== Using the Denied Result From the Method Invocation - -There are some scenarios where you might want to return a secure result derived from the denied result. -For example, if a user is not authorized to see email addresses, you might want to apply some masking on the original email address, i.e. _useremail@example.com_ would become _use\\******@example.com_. - -For those scenarios, you can override the `handleDeniedInvocationResult` from the `MethodAuthorizationDeniedHandler`, which has the {security-api-url}org/springframework/security/authorization/method/MethodInvocationResult.html[`MethodInvocationResult`] as an argument. -Let's continue with the previous example, but instead of returning `null`, we will return a masked value of the email: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -public class EmailMaskingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1> - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - return "***"; - } - - @Override - public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { - String email = (String) methodInvocationResult.getResult(); - return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*"); - } - -} - -@Configuration -@EnableMethodSecurity -public class SecurityConfig { - - @Bean <2> - public EmailMaskingMethodAuthorizationDeniedHandler emailMaskingMethodAuthorizationDeniedHandler() { - return new EmailMaskingMethodAuthorizationDeniedHandler(); - } - -} - -public class User { - // ... - - @PostAuthorize(value = "hasAuthority('user:read')") - @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler.class) - public String getEmail() { - return this.email; - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -class EmailMaskingMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler { - - override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any { - return "***" - } - - override fun handleDeniedInvocationResult(methodInvocationResult: MethodInvocationResult, authorizationResult: AuthorizationResult): Any { - val email = methodInvocationResult.result as String - return email.replace("(^[^@]{3}|(?!^)\\G)[^@]".toRegex(), "$1*") - } - -} - -@Configuration -@EnableMethodSecurity -class SecurityConfig { - - @Bean - fun emailMaskingMethodAuthorizationDeniedHandler(): EmailMaskingMethodAuthorizationDeniedHandler { - return EmailMaskingMethodAuthorizationDeniedHandler() - } - -} - -class User (val name:String, @PostAuthorize(value = "hasAuthority('user:read')") @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler::class) val email:String) <3> ----- -====== - -<1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a masked value of the unauthorized result value -<2> Register the `EmailMaskingMethodAuthorizationDeniedHandler` as a bean -<3> Annotate the method with `@HandleAuthorizationDenied` and pass the `EmailMaskingMethodAuthorizationDeniedHandler` to the `handlerClass` attribute - -And then you can verify that a masked email is returned instead of an `AccessDeniedException`: - -[WARNING] -==== -Since you have access to the original denied value, make sure that you correctly handle it and do not return it to the caller. -==== - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Autowired -UserRepository users; - -@Test -void getEmailWhenProxiedThenMaskedEmail() { - Optional securedUser = users.findByName("name"); - // email is useremail@example.com - assertThat(securedUser.get().getEmail()).isEqualTo("use******@example.com"); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Autowired -var users:UserRepository? = null - -@Test -fun getEmailWhenProxiedThenMaskedEmail() { - val securedUser: Optional = users.findByName("name") - // email is useremail@example.com - assertThat(securedUser.get().getEmail()).isEqualTo("use******@example.com") -} ----- -====== - -When implementing the `MethodAuthorizationDeniedHandler` you have a few options on what type you can return: - -- A `null` value. -- A non-null value, respecting the method's return type. -- Throw an exception, usually an instance of `AuthorizationDeniedException`. This is the default behavior. -- A `Mono` type for reactive applications. - -Note that since the handler must be registered as beans in your application context, you can inject dependencies into them if you need a more complex logic. -In addition to that, you have available the `MethodInvocation` or the `MethodInvocationResult`, as well as the `AuthorizationResult` for more details related to the authorization decision. - -[[deciding-return-based-parameters]] -=== Deciding What to Return Based on Available Parameters - -Consider a scenario where there might be multiple mask values for different methods, it would be not so productive if we had to create a handler for each of those methods, although it is perfectly fine to do that. -In such cases, we can use the information passed via parameters to decide what to do. -For example, we can create a custom `@Mask` annotation and a handler that detects that annotation to decide what mask value to return: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -import org.springframework.core.annotation.AnnotationUtils; - -@Target({ ElementType.METHOD, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface Mask { - - String value(); - -} - -public class MaskAnnotationDeniedHandler implements MethodAuthorizationDeniedHandler { - - @Override - public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class); - return mask.value(); - } - -} - -@Configuration -@EnableMethodSecurity -public class SecurityConfig { - - @Bean - public MaskAnnotationDeniedHandler maskAnnotationDeniedHandler() { - return new MaskAnnotationDeniedHandler(); - } - -} - -@Component -public class MyService { - - @PreAuthorize(value = "hasAuthority('user:read')") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class) - @Mask("***") - public String foo() { - return "foo"; - } - - @PreAuthorize(value = "hasAuthority('user:read')") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class) - @Mask("???") - public String bar() { - return "bar"; - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -import org.springframework.core.annotation.AnnotationUtils - -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -annotation class Mask(val value: String) - -class MaskAnnotationDeniedHandler : MethodAuthorizationDeniedHandler { - - override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any { - val mask = AnnotationUtils.getAnnotation(methodInvocation.method, Mask::class.java) - return mask.value - } - -} - -@Configuration -@EnableMethodSecurity -class SecurityConfig { - - @Bean - fun maskAnnotationDeniedHandler(): MaskAnnotationDeniedHandler { - return MaskAnnotationDeniedHandler() - } - -} - -@Component -class MyService { - - @PreAuthorize(value = "hasAuthority('user:read')") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class) - @Mask("***") - fun foo(): String { - return "foo" - } - - @PreAuthorize(value = "hasAuthority('user:read')") - @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class) - @Mask("???") - fun bar(): String { - return "bar" - } - -} ----- -====== - -Now the return values when access is denied will be decided based on the `@Mask` annotation: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Autowired -MyService myService; - -@Test -void fooWhenDeniedThenReturnStars() { - String value = this.myService.foo(); - assertThat(value).isEqualTo("***"); -} - -@Test -void barWhenDeniedThenReturnQuestionMarks() { - String value = this.myService.foo(); - assertThat(value).isEqualTo("???"); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Autowired -var myService: MyService - -@Test -fun fooWhenDeniedThenReturnStars() { - val value: String = myService.foo() - assertThat(value).isEqualTo("***") -} - -@Test -fun barWhenDeniedThenReturnQuestionMarks() { - val value: String = myService.foo() - assertThat(value).isEqualTo("???") -} ----- -====== - -=== Combining with Meta Annotation Support - -You can also combine the `@HandleAuthorizationDenied` with other annotations in order to reduce and simplify the annotations in a method. -Let's consider the <> and merge `@HandleAuthorizationDenied` with `@Mask`: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Target({ ElementType.METHOD, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class) -public @interface Mask { - - String value(); - -} - -@Mask("***") -public String myMethod() { - // ... -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class) -annotation class Mask(val value: String) - -@Mask("***") -fun myMethod(): String { - // ... -} ----- -====== - -Now you do not have to remember to add both annotations when you need a mask behavior in your method. -Make sure to read the <> section for more details on the usage. - [[migration-enableglobalmethodsecurity]] == Migrating from `@EnableGlobalMethodSecurity` diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc index e7a5e1022ac..25dd1e8f394 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc @@ -27,7 +27,7 @@ The `OAuth2AuthorizationRequestRedirectFilter` uses an `OAuth2AuthorizationReque The primary role of the `OAuth2AuthorizationRequestResolver` is to resolve an `OAuth2AuthorizationRequest` from the provided web request. The default implementation `DefaultOAuth2AuthorizationRequestResolver` matches on the (default) path `+/oauth2/authorization/{registrationId}+`, extracting the `registrationId`, and using it to build the `OAuth2AuthorizationRequest` for the associated `ClientRegistration`. -Consider the following Spring Boot properties for an OAuth 2.0 Client registration: +Consider the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml,attrs="-attributes"] ---- @@ -740,7 +740,7 @@ which is an implementation of an `OAuth2AuthorizedClientProvider` for the Client === Using the Access Token -Consider the following Spring Boot properties for an OAuth 2.0 Client registration: +Consider the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml] ---- @@ -1005,7 +1005,7 @@ which is an implementation of an `OAuth2AuthorizedClientProvider` for the Resour === Using the Access Token -Consider the following Spring Boot properties for an OAuth 2.0 Client registration: +Consider the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml] ---- @@ -1309,7 +1309,7 @@ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) === Using the Access Token -Given the following Spring Boot properties for an OAuth 2.0 Client registration: +Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml] ---- @@ -1435,255 +1435,3 @@ class OAuth2ResourceServerController { [TIP] If you need to resolve the `Jwt` assertion from a different source, you can provide `JwtBearerOAuth2AuthorizedClientProvider.setJwtAssertionResolver()` with a custom `Function`. - -[[oauth2Client-token-exchange-grant]] -== Token Exchange - -[NOTE] -==== -Please refer to OAuth 2.0 Token Exchange for further details on the https://datatracker.ietf.org/doc/html/rfc8693[Token Exchange] grant. -==== - - -=== Requesting an Access Token - -[NOTE] -==== -Please refer to the https://datatracker.ietf.org/doc/html/rfc8693#section-2[Token Exchange Request and Response] protocol flow for the Token Exchange grant. -==== - -The default implementation of `OAuth2AccessTokenResponseClient` for the Token Exchange grant is `DefaultTokenExchangeTokenResponseClient`, which uses a `RestOperations` when requesting an access token at the Authorization Server’s Token Endpoint. - -The `DefaultTokenExchangeTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response. - - -=== Customizing the Access Token Request - -If you need to customize the pre-processing of the Token Request, you can provide `DefaultTokenExchangeTokenResponseClient.setRequestEntityConverter()` with a custom `Converter>`. -The default implementation `TokenExchangeGrantRequestEntityConverter` builds a `RequestEntity` representation of a https://datatracker.ietf.org/doc/html/rfc8693#section-2.1[OAuth 2.0 Access Token Request]. -However, providing a custom `Converter`, would allow you to extend the Token Request and add custom parameter(s). - -To customize only the parameters of the request, you can provide `TokenExchangeGrantRequestEntityConverter.setParametersConverter()` with a custom `Converter>` to completely override the parameters sent with the request. This is often simpler than constructing a `RequestEntity` directly. - -[TIP] -If you prefer to only add additional parameters, you can provide `TokenExchangeGrantRequestEntityConverter.addParametersConverter()` with a custom `Converter>` which constructs an aggregate `Converter`. - - -=== Customizing the Access Token Response - -On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultTokenExchangeTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`. -The default `RestOperations` is configured as follows: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -RestTemplate restTemplate = new RestTemplate(Arrays.asList( - new FormHttpMessageConverter(), - new OAuth2AccessTokenResponseHttpMessageConverter())); - -restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val restTemplate = RestTemplate(listOf( - FormHttpMessageConverter(), - OAuth2AccessTokenResponseHttpMessageConverter())) - -restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler() ----- -====== - -[TIP] -==== -Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request. -==== - -`OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response. -You can provide `OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter()` with a custom `Converter, OAuth2AccessTokenResponse>` that is used for converting the OAuth 2.0 Access Token Response parameters to an `OAuth2AccessTokenResponse`. - -`OAuth2ErrorResponseErrorHandler` is a `ResponseErrorHandler` that can handle an OAuth 2.0 Error, eg. 400 Bad Request. -It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error parameters to an `OAuth2Error`. - -Whether you customize `DefaultTokenExchangeTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -// Customize -OAuth2AccessTokenResponseClient tokenExchangeTokenResponseClient = ... - -TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider(); -tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient); - -OAuth2AuthorizedClientProvider authorizedClientProvider = - OAuth2AuthorizedClientProviderBuilder.builder() - .provider(tokenExchangeAuthorizedClientProvider) - .build(); - -... - -authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -// Customize -val tokenExchangeTokenResponseClient: OAuth2AccessTokenResponseClient = ... - -val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider() -tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient) - -val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() - .provider(tokenExchangeAuthorizedClientProvider) - .build() - -... - -authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) ----- -====== - -[[token-exchange-grant-access-token]] -=== Using the Access Token - -Given the following Spring Boot properties for an OAuth 2.0 Client registration: - -[source,yaml] ----- -spring: - security: - oauth2: - client: - registration: - okta: - client-id: okta-client-id - client-secret: okta-client-secret - authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange - scope: read - provider: - okta: - token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token ----- - -...and the `OAuth2AuthorizedClientManager` `@Bean`: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -public OAuth2AuthorizedClientManager authorizedClientManager( - ClientRegistrationRepository clientRegistrationRepository, - OAuth2AuthorizedClientRepository authorizedClientRepository) { - - TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = - new TokenExchangeOAuth2AuthorizedClientProvider(); - - OAuth2AuthorizedClientProvider authorizedClientProvider = - OAuth2AuthorizedClientProviderBuilder.builder() - .provider(tokenExchangeAuthorizedClientProvider) - .build(); - - DefaultOAuth2AuthorizedClientManager authorizedClientManager = - new DefaultOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); - - return authorizedClientManager; -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun authorizedClientManager( - clientRegistrationRepository: ClientRegistrationRepository, - authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager { - val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider() - val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() - .provider(tokenExchangeAuthorizedClientProvider) - .build() - val authorizedClientManager = DefaultOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository) - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) - return authorizedClientManager -} ----- -====== - -You may obtain the `OAuth2AccessToken` as follows: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@RestController -public class OAuth2ResourceServerController { - - @Autowired - private OAuth2AuthorizedClientManager authorizedClientManager; - - @GetMapping("/resource") - public String resource(JwtAuthenticationToken jwtAuthentication) { - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta") - .principal(jwtAuthentication) - .build(); - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); - - ... - - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -class OAuth2ResourceServerController { - - @Autowired - private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager - - @GetMapping("/resource") - fun resource(jwtAuthentication: JwtAuthenticationToken?): String { - val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta") - .principal(jwtAuthentication) - .build() - val authorizedClient = authorizedClientManager.authorize(authorizeRequest) - val accessToken: OAuth2AccessToken = authorizedClient.accessToken - - ... - - } -} ----- -====== - -[NOTE] -`TokenExchangeOAuth2AuthorizedClientProvider` resolves the subject token (as an `OAuth2Token`) via `OAuth2AuthorizationContext.getPrincipal().getPrincipal()` by default, hence the use of `JwtAuthenticationToken` in the preceding example. -An actor token is not resolved by default. - -[TIP] -If you need to resolve the subject token from a different source, you can provide `TokenExchangeOAuth2AuthorizedClientProvider.setSubjectTokenResolver()` with a custom `Function`. - -[TIP] -If you need to resolve an actor token, you can provide `TokenExchangeOAuth2AuthorizedClientProvider.setActorTokenResolver()` with a custom `Function`. diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/client-authentication.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/client-authentication.adoc index ed5c82ca034..c9fe6e27d29 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/client-authentication.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/client-authentication.adoc @@ -18,7 +18,7 @@ is supplied by the `com.nimbusds.jose.jwk.JWK` resolver associated with `NimbusJ === Authenticate using `private_key_jwt` -Given the following Spring Boot properties for an OAuth 2.0 Client registration: +Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml] ---- @@ -96,7 +96,7 @@ tokenResponseClient.setRequestEntityConverter(requestEntityConverter) === Authenticate using `client_secret_jwt` -Given the following Spring Boot properties for an OAuth 2.0 Client registration: +Given the following Spring Boot 2.x properties for an OAuth 2.0 Client registration: [source,yaml] ---- diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc index 375c8a12a89..eb0cfae56d4 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc @@ -60,7 +60,7 @@ The name may be used in certain scenarios, such as when displaying the name of t which contains the cryptographic key(s) used to verify the https://tools.ietf.org/html/rfc7515[JSON Web Signature (JWS)] of the ID Token and (optionally) the UserInfo Response. <12> `issuerUri`: Returns the issuer identifier URI for the OpenID Connect 1.0 provider or the OAuth 2.0 Authorization Server. <13> `configurationMetadata`: The https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Provider Configuration Information]. -This information is available only if the Spring Boot property `spring.security.oauth2.client.provider.[providerId].issuerUri` is configured. +This information is available only if the Spring Boot 2.x property `spring.security.oauth2.client.provider.[providerId].issuerUri` is configured. <14> `(userInfoEndpoint)uri`: The UserInfo Endpoint URI used to access the claims and attributes of the authenticated end-user. <15> `(userInfoEndpoint)authenticationMethod`: The authentication method used when sending the access token to the UserInfo Endpoint. The supported values are *header*, *form*, and *query*. @@ -103,7 +103,7 @@ Client registration information is ultimately stored and owned by the associated This repository provides the ability to retrieve a subset of the primary client registration information, which is stored with the Authorization Server. ==== -Spring Boot auto-configuration binds each of the properties under `spring.security.oauth2.client.registration._[registrationId]_` to an instance of `ClientRegistration` and then composes each of the `ClientRegistration` instance(s) within a `ClientRegistrationRepository`. +Spring Boot 2.x auto-configuration binds each of the properties under `spring.security.oauth2.client.registration._[registrationId]_` to an instance of `ClientRegistration` and then composes each of the `ClientRegistration` instance(s) within a `ClientRegistrationRepository`. [NOTE] ==== @@ -231,7 +231,7 @@ class OAuth2ClientController { [NOTE] ==== -Spring Boot auto-configuration registers an `OAuth2AuthorizedClientRepository` or an `OAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`. +Spring Boot 2.x auto-configuration registers an `OAuth2AuthorizedClientRepository` or an `OAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`. However, the application can override and register a custom `OAuth2AuthorizedClientRepository` or `OAuth2AuthorizedClientService` `@Bean`. ==== diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc index 565a719aa8c..3adac445889 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc @@ -12,7 +12,6 @@ At a high-level, the core features available are: * https://tools.ietf.org/html/rfc6749#section-1.3.4[Client Credentials] * https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials] * https://datatracker.ietf.org/doc/html/rfc7523#section-2.1[JWT Bearer] -* https://datatracker.ietf.org/doc/html/rfc8693#section-2.1[Token Exchange] .Client Authentication support * https://datatracker.ietf.org/doc/html/rfc7523#section-2.2[JWT Bearer] diff --git a/docs/modules/ROOT/pages/servlet/oauth2/index.adoc b/docs/modules/ROOT/pages/servlet/oauth2/index.adoc index c1e81238f5e..512400577d0 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/index.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/index.adoc @@ -491,7 +491,7 @@ With the above configuration, the application now supports two additional endpoi ==== The presence of the `openid` scope in the above configuration indicates that OpenID Connect 1.0 should be used. This instructs Spring Security to use OIDC-specific components (such as `OidcUserService`) during request processing. -Without this scope, Spring Security will use OAuth2-specific components (such as `DefaultOAuth2UserService`) instead. +Without this scope, Spring Security will use OAuth2-specific components (such as `OAuth2UserService`) instead. ==== [[oauth2-client-access-protected-resources]] @@ -708,7 +708,7 @@ class MessagesController(private val webClient: WebClient) { .uri("http://localhost:8090/messages") .attributes(clientRegistrationId("my-oauth2-client")) .retrieve() - .toEntityList() + .toEntityList(Message::class.java) .block()!! } @@ -933,7 +933,7 @@ class MessagesController(private val webClient: WebClient) { return webClient.get() .uri("http://localhost:8090/messages") .retrieve() - .toEntityList() + .toEntityList(Message::class.java) .block()!! } @@ -953,7 +953,7 @@ This is because it can be derived from the currently logged in user. === Enable an Extension Grant Type A common use case involves enabling and/or configuring an extension grant type. -For example, Spring Security provides support for the `jwt-bearer` and `token-exchange` grant types, but does not enable them by default because they are not part of the core OAuth 2.0 specification. +For example, Spring Security provides support for the `jwt-bearer` grant type, but does not enable it by default because it is not part of the core OAuth 2.0 specification. With Spring Security 6.2 and later, we can simply publish a bean for one or more `OAuth2AuthorizedClientProvider` and they will be picked up automatically. The following example simply enables the `jwt-bearer` grant type: @@ -1356,18 +1356,12 @@ Spring Security automatically resolves the following generic types of `OAuth2Acc * `OAuth2ClientCredentialsGrantRequest` (see `DefaultClientCredentialsTokenResponseClient`) * `OAuth2PasswordGrantRequest` (see `DefaultPasswordTokenResponseClient`) * `JwtBearerGrantRequest` (see `DefaultJwtBearerTokenResponseClient`) -* `TokenExchangeGrantRequest` (see `DefaultTokenExchangeTokenResponseClient`) [TIP] ==== Publishing a bean of type `OAuth2AccessTokenResponseClient` will automatically enable the `jwt-bearer` grant type without the need to <>. ==== -[TIP] -==== -Publishing a bean of type `OAuth2AccessTokenResponseClient` will automatically enable the `token-exchange` grant type without the need to <>. -==== - [[oauth2-client-customize-rest-operations]] === Customize the `RestOperations` used by OAuth2 Client Components @@ -1433,15 +1427,6 @@ public class SecurityConfig { return accessTokenResponseClient; } - @Bean - public OAuth2AccessTokenResponseClient tokenExchangeAccessTokenResponseClient() { - DefaultTokenExchangeTokenResponseClient accessTokenResponseClient = - new DefaultTokenExchangeTokenResponseClient(); - accessTokenResponseClient.setRestOperations(restTemplate()); - - return accessTokenResponseClient; - } - @Bean public RestTemplate restTemplate() { // ... @@ -1497,14 +1482,6 @@ class SecurityConfig { return accessTokenResponseClient } - @Bean - fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient { - val accessTokenResponseClient = DefaultTokenExchangeTokenResponseClient() - accessTokenResponseClient.setRestOperations(restTemplate()) - - return accessTokenResponseClient - } - @Bean fun restTemplate(): RestTemplate { // ... @@ -1584,14 +1561,6 @@ public class SecurityConfig { new JwtBearerOAuth2AuthorizedClientProvider(); jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient); - DefaultTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient = - new DefaultTokenExchangeTokenResponseClient(); - tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate()); - - TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = - new TokenExchangeOAuth2AuthorizedClientProvider(); - tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient); - OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() @@ -1605,7 +1574,6 @@ public class SecurityConfig { .accessTokenResponseClient(passwordAccessTokenResponseClient) ) .provider(jwtBearerAuthorizedClientProvider) - .provider(tokenExchangeAuthorizedClientProvider) .build(); DefaultOAuth2AuthorizedClientManager authorizedClientManager = @@ -1676,12 +1644,6 @@ class SecurityConfig { val jwtBearerAuthorizedClientProvider = JwtBearerOAuth2AuthorizedClientProvider() jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient) - val tokenExchangeAccessTokenResponseClient = DefaultTokenExchangeTokenResponseClient() - tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate()) - - val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider() - tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient) - val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() .refreshToken { refreshToken -> @@ -1694,7 +1656,6 @@ class SecurityConfig { password.accessTokenResponseClient(passwordAccessTokenResponseClient) } .provider(jwtBearerAuthorizedClientProvider) - .provider(tokenExchangeAuthorizedClientProvider) .build() val authorizedClientManager = DefaultOAuth2AuthorizedClientManager( diff --git a/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc b/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc index 53c9381026c..9d5fa9918a0 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc @@ -660,13 +660,7 @@ public class OAuth2LoginSecurityConfig { // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities // 3) Create a copy of oidcUser but use the mappedAuthorities instead - ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails(); - String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName(); - if (StringUtils.hasText(userNameAttributeName)) { - oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo(), userNameAttributeName); - } else { - oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo()); - } + oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo()); return oidcUser; }; @@ -700,7 +694,7 @@ class OAuth2LoginSecurityConfig { return OAuth2UserService { userRequest -> // Delegate to the default implementation for loading a user - val oidcUser = delegate.loadUser(userRequest) + var oidcUser = delegate.loadUser(userRequest) val accessToken = userRequest.accessToken val mappedAuthorities = HashSet() @@ -709,13 +703,9 @@ class OAuth2LoginSecurityConfig { // 1) Fetch the authority information from the protected resource using accessToken // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities // 3) Create a copy of oidcUser but use the mappedAuthorities instead - val providerDetails = userRequest.getClientRegistration().getProviderDetails() - val userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName() - if (StringUtils.hasText(userNameAttributeName)) { - DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo, userNameAttributeName) - } else { - DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo) - } + oidcUser = DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo) + + oidcUser } } } @@ -940,4 +930,4 @@ If more than one `ClientRegistration` is configured for OpenID Connect 1.0 Authe ==== [[oauth2login-advanced-oidc-logout]] -Then, you can proceed to configure xref:servlet/oauth2/login/logout.adoc[logout] +Then, you can proceed to configure xref:reactive/oauth2/login/logout.adoc[logout] diff --git a/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc b/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc index ffcecee7c93..f2ad1575cf4 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc @@ -1,9 +1,9 @@ = Core Configuration [[oauth2login-sample-boot]] -== Spring Boot Sample +== Spring Boot 2.x Sample -Spring Boot brings full auto-configuration capabilities for OAuth 2.0 Login. +Spring Boot 2.x brings full auto-configuration capabilities for OAuth 2.0 Login. This section shows how to configure the {gh-samples-url}/servlet/spring-boot/java/oauth2/login[*OAuth 2.0 Login sample*] by using _Google_ as the _Authentication Provider_ and covers the following topics: @@ -78,7 +78,7 @@ spring: [[oauth2login-sample-boot-application]] === Boot up the Application -Launch the Spring Boot sample and go to `http://localhost:8080`. +Launch the Spring Boot 2.x sample and go to `http://localhost:8080`. You are then redirected to the default _auto-generated_ login page, which displays a link for Google. Click on the Google link, and you are then redirected to Google for authentication. @@ -91,12 +91,12 @@ At this point, the OAuth Client retrieves your email address and basic profile i [[oauth2login-boot-property-mappings]] -== Spring Boot Property Mappings +== Spring Boot 2.x Property Mappings -The following table outlines the mapping of the Spring Boot OAuth Client properties to the xref:servlet/oauth2/client/index.adoc#oauth2Client-client-registration[ClientRegistration] properties. +The following table outlines the mapping of the Spring Boot 2.x OAuth Client properties to the xref:servlet/oauth2/client/index.adoc#oauth2Client-client-registration[ClientRegistration] properties. |=== -|Spring Boot |ClientRegistration +|Spring Boot 2.x |ClientRegistration |`spring.security.oauth2.client.registration._[registrationId]_` |`registrationId` @@ -203,7 +203,7 @@ There are some OAuth 2.0 Providers that support multi-tenancy, which results in For example, an OAuth Client registered with Okta is assigned to a specific sub-domain and have their own protocol endpoints. -For these cases, Spring Boot provides the following base property for configuring custom provider properties: `spring.security.oauth2.client.provider._[providerId]_`. +For these cases, Spring Boot 2.x provides the following base property for configuring custom provider properties: `spring.security.oauth2.client.provider._[providerId]_`. The following listing shows an example: @@ -228,9 +228,9 @@ spring: <1> The base property (`spring.security.oauth2.client.provider.okta`) allows for custom configuration of protocol endpoint locations. [[oauth2login-override-boot-autoconfig]] -== Overriding Spring Boot Auto-configuration +== Overriding Spring Boot 2.x Auto-configuration -The Spring Boot auto-configuration class for OAuth Client support is `OAuth2ClientAutoConfiguration`. +The Spring Boot 2.x auto-configuration class for OAuth Client support is `OAuth2ClientAutoConfiguration`. It performs the following tasks: @@ -457,9 +457,9 @@ class OAuth2LoginConfig { [[oauth2login-javaconfig-wo-boot]] -== Java Configuration without Spring Boot +== Java Configuration without Spring Boot 2.x -If you are not able to use Spring Boot and would like to configure one of the pre-defined providers in `CommonOAuth2Provider` (for example, Google), apply the following configuration: +If you are not able to use Spring Boot 2.x and would like to configure one of the pre-defined providers in `CommonOAuth2Provider` (for example, Google), apply the following configuration: .OAuth2 Login Configuration [tabs] diff --git a/docs/modules/ROOT/pages/servlet/saml2/logout.adoc b/docs/modules/ROOT/pages/servlet/saml2/logout.adoc index 34038df70c6..d737bf578a2 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/logout.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/logout.adoc @@ -1,7 +1,7 @@ [[servlet-saml2login-logout]] = Performing Single Logout -Among its xref:servlet/authentication/logout.adoc[other logout mechanisms], Spring Security ships with support for RP- and AP-initiated SAML 2.0 Single Logout. +Spring Security ships with support for RP- and AP-initiated SAML 2.0 Single Logout. Briefly, there are two use cases Spring Security supports: @@ -22,201 +22,61 @@ To use Spring Security's SAML 2.0 Single Logout feature, you will need the follo * Second, the asserting party should be configured to sign and POST `saml2:LogoutRequest` s and `saml2:LogoutResponse` s your application's `/logout/saml2/slo` endpoint * Third, your application must have a PKCS#8 private key and X.509 certificate for signing `saml2:LogoutRequest` s and `saml2:LogoutResponse` s -You can achieve this in Spring Boot in the following way: +You can begin from the initial minimal example and add the following configuration: -[source,yaml] +[source,java] ---- -spring: - security: - saml2: - relyingparty: - registration: - metadata: - signing.credentials: <3> - - private-key-location: classpath:credentials/rp-private.key - certificate-location: classpath:credentials/rp-certificate.crt - singlelogout.url: "{baseUrl}/logout/saml2/slo" <2> - assertingparty: - metadata-uri: https://ap.example.com/metadata <1> +@Value("${private.key}") RSAPrivateKey key; +@Value("${public.certificate}") X509Certificate certificate; ----- -<1> - The metadata URI of the IDP, which will indicate to your application its support of SLO -<2> - The SLO endpoint in your application -<3> - The signing credentials to sign ````s and ````s - -[NOTE] ----- -An asserting party supports Single Logout if their metadata includes the `` element in their metadata. ----- - -And that's it! - -Spring Security's logout support offers a number of configuration points. -Consider the following use cases: - -* Understand how the above <<_startup_expectations, minimal configuration works>> -* Get a picture of <> -* Allow users to <> -* Customize <<_configuring_logout_endpoints, logout endpoints>> -* Storing `` somewhere <<_customizing_storage, other than the session>> - -=== Startup Expectations - -When these properties are used, in addition to login, SAML 2.0 Service Provider will automatically configure itself facilitate logout by way of ````s and ````s using either RP- or AP-initiated logout. - -It achieves this through a deterministic startup process: - -1. Query the Identity Server Metadata endpoint for the `` element -2. Scan the metadata and cache any public signature verification keys -3. Prepare the appropriate endpoints +@Bean +RelyingPartyRegistrationRepository registrations() { + Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate); + RelyingPartyRegistration registration = RelyingPartyRegistrations + .fromMetadataLocation("https://ap.example.org/metadata") + .registrationId("id") + .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") + .signingX509Credentials((signing) -> signing.add(credential)) <1> + .build(); + return new InMemoryRelyingPartyRegistrationRepository(registration); +} -A consequence of this process is that the identity server must be up and receiving requests in order for Service Provider to successfully start up. +@Bean +SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception { + http + .authorizeHttpRequests((authorize) -> authorize + .anyRequest().authenticated() + ) + .saml2Login(withDefaults()) + .saml2Logout(withDefaults()); <2> -[NOTE] -If the identity server is down when Service Provider queries it (given appropriate timeouts), then startup will fail. + return http.build(); +} +---- +<1> - First, add your signing key to the `RelyingPartyRegistration` instance or to xref:servlet/saml2/login/overview.adoc#servlet-saml2login-rpr-duplicated[multiple instances] +<2> - Second, indicate that your application wants to use SAML SLO to logout the end user === Runtime Expectations -Given the above configuration any logged-in user can send a `POST /logout` to your application to perform RP-initiated SLO. +Given the above configuration any logged in user can send a `POST /logout` to your application to perform RP-initiated SLO. Your application will then do the following: 1. Logout the user and invalidate the session -2. Produce a `` and POST it to the associated asserting party's SLO endpoint -3. Then, if the asserting party responds with a ``, the application with verify it and redirect to the configured success endpoint +2. Use a `Saml2LogoutRequestResolver` to create, sign, and serialize a `` based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] associated with the currently logged-in user. +3. Send a redirect or post to the asserting party based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] +4. Deserialize, verify, and process the `` sent by the asserting party +5. Redirect to any configured successful logout endpoint -Also, your application can participate in an AP-initiated logout when the asserting party sends a `` to `/logout/saml2/slo`. -When this happens, your application will do the following: +Also, your application can participate in an AP-initiated logout when the asserting party sends a `` to `/logout/saml2/slo`: -1. Verify the `` +1. Use a `Saml2LogoutRequestHandler` to deserialize, verify, and process the `` sent by the asserting party 2. Logout the user and invalidate the session -3. Produce a `` and POST it back to the asserting party's SLO endpoint - -== Minimal Configuration Sans Boot - -Instead of Boot properties, you can also achieve the same outcome by publishing the beans directly like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -public class SecurityConfig { - @Value("${private.key}") RSAPrivateKey key; - @Value("${public.certificate}") X509Certificate certificate; - - @Bean - RelyingPartyRegistrationRepository registrations() { - Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate); - RelyingPartyRegistration registration = RelyingPartyRegistrations - .fromMetadataLocation("https://ap.example.org/metadata") <1> - .registrationId("metadata") - .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") <2> - .signingX509Credentials((signing) -> signing.add(credential)) <3> - .build(); - return new InMemoryRelyingPartyRegistrationRepository(registration); - } +3. Create, sign, and serialize a `` based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] associated with the just logged-out user +4. Send a redirect or post to the asserting party based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] - @Bean - SecurityFilterChain web(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .saml2Login(withDefaults()) - .saml2Logout(withDefaults()); <4> - - return http.build(); - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Configuration -class SecurityConfig(@Value("${private.key}") val key: RSAPrivateKey, - @Value("${public.certificate}") val certificate: X509Certificate) { - - @Bean - fun registrations(): RelyingPartyRegistrationRepository { - val credential = Saml2X509Credential.signing(key, certificate) - val registration = RelyingPartyRegistrations - .fromMetadataLocation("https://ap.example.org/metadata") <1> - .registrationId("metadata") - .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") <2> - .signingX509Credentials({ signing: List -> signing.add(credential) }) <3> - .build() - return InMemoryRelyingPartyRegistrationRepository(registration) - } - - @Bean - fun web(http: HttpSecurity): SecurityFilterChain { - http { - authorizeHttpRequests { - anyRequest = authenticated - } - saml2Login { - - } - saml2Logout { <4> - - } - } - - return http.build() - } -} ----- -====== -<1> - The metadata URI of the IDP, which will indicate to your application its support of SLO -<2> - The SLO endpoint in your application -<3> - The signing credentials to sign ````s and ````s, which you can also add to xref:servlet/saml2/login/overview.adoc#servlet-saml2login-rpr-duplicated[multiple relying parties] -<4> - Second, indicate that your application wants to use SAML SLO to logout the end user - -[NOTE] -Adding `saml2Logout` adds the capability for logout to your service provider as a whole. +NOTE: Adding `saml2Logout` adds the capability for logout to the service provider. Because it is an optional capability, you need to enable it for each individual `RelyingPartyRegistration`. -You do this by setting the `RelyingPartyRegistration.Builder#singleLogoutServiceLocation` property as seen above. - -[[architecture]] -== How Saml 2.0 Logout Works - -Next, let's see the architectural components that Spring Security uses to support https://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf#page=37[SAML 2.0 Logout] in servlet-based applications, like the one we just saw. - -For RP-initiated logout: - -image:{icondir}/number_1.png[] Spring Security executes its xref:servlet/authentication/logout.adoc#logout-architecture[logout flow], calling its ``LogoutHandler``s to invalidate the session and perform other cleanup. -It then invokes the {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2RelyingPartyInitiatedLogoutSuccessHandler.html[`Saml2RelyingPartyInitiatedLogoutSuccessHandler`]. - -image:{icondir}/number_2.png[] The logout success handler uses an instance of -{security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestResolver.html[`Saml2LogoutRequestResolver`] to create, sign, and serialize a ``. -It uses the keys and configuration from the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] that is associated with the current `Saml2AuthenticatedPrincipal`. -Then, it redirect-POSTs the `` to the asserting party SLO endpoint - -The browser hands control over to the asserting party. -If the asserting party redirects back (which it may not), then the application proceeds to step image:{icondir}/number_3.png[]. - -image:{icondir}/number_3.png[] The {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilter.html[`Saml2LogoutResponseFilter`] deserializes, verifies, and processes the `` with its {security-api-url}org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutResponseValidator.html[`Saml2LogoutResponseValidator`]. - -image:{icondir}/number_4.png[] If valid, then it completes the local logout flow by redirecting to `/login?logout`, or whatever has been configured. -If invalid, then it responds with a 400. - -For AP-initiated logout: - -image:{icondir}/number_1.png[] The {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.html[`Saml2LogoutRequestFilter`] deserializes, verifies, and processes the `` with its {security-api-url}org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutRequestValidator.html[`Saml2LogoutRequestValidator`]. - -image:{icondir}/number_2.png[] If valid, then the filter calls the configured ``LogoutHandler``s, invalidating the session and performing other cleanup. - -image:{icondir}/number_3.png[] It uses a {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.html[`Saml2LogoutResponseResolver`] to create, sign and serialize a ``. -It uses the keys and configuration from the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] derived from the endpoint or from the contents of the ``. -Then, it redirect-POSTs the `` to the asserting party SLO endpoint. - -The browser hands control over to the asserting party. - -image:{icondir}/number_4.png[] If invalid, then it https://github.com/spring-projects/spring-security/pull/14676[responds with a 400]. +You can do this by setting the `RelyingPartyRegistration.Builder#singleLogoutServiceLocation` property. == Configuring Logout Endpoints @@ -252,87 +112,10 @@ http .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2")) ); ---- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - saml2Logout { - logoutRequest { - logoutUrl = "/SLOService.saml2" - } - logoutResponse { - logoutUrl = "/SLOService.saml2" - } - } -} ----- ====== You should also configure these endpoints in your `RelyingPartyRegistration`. -Also, you can customize the endpoint for triggering logout locally like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -http - .saml2Logout((saml2) -> saml2.logoutUrl("/saml2/logout")); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - saml2Logout { - logoutUrl = "/saml2/logout" - } -} ----- -====== - -[[separating-local-saml2-logout]] -=== Separating Local Logout from SAML 2.0 Logout - -In some cases, you may want to expose one logout endpoint for local logout and another for RP-initiated SLO. -Like is the case with other logout mechanisms, you can register more than one, so long as they each have a different endpoint. - -So, for example, you can wire the DSL like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -http - .logout((logout) -> logout.logoutUrl("/logout")) - .saml2Logout((saml2) -> saml2.logoutUrl("/saml2/logout")); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - logout { - logoutUrl = "/logout" - } - saml2Logout { - logoutUrl = "/saml2/logout" - } -} ----- -====== - -and now if a client sends a `POST /logout`, the session will be cleared, but there won't be a `` sent to the asserting party. -But, if the client sends a `POST /saml2/logout`, then the application will initiate SAML 2.0 SLO as normal. - == Customizing `` Resolution It's common to need to set other values in the `` than the defaults that Spring Security provides. @@ -346,11 +129,7 @@ By default, Spring Security will issue a `` and supply: To add other values, you can use delegation, like so: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] +[source,java] ---- @Bean Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) { @@ -368,33 +147,9 @@ Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationReposit } ---- -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun logoutRequestResolver(registrations:RelyingPartyRegistrationRepository?): Saml2LogoutRequestResolver { - val logoutRequestResolver = OpenSaml4LogoutRequestResolver(registrations) - logoutRequestResolver.setParametersConsumer { parameters: LogoutRequestParameters -> - val name: String = (parameters.getAuthentication().getPrincipal() as Saml2AuthenticatedPrincipal).getFirstAttribute("CustomAttribute") - val format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" - val logoutRequest: LogoutRequest = parameters.getLogoutRequest() - val nameId: NameID = logoutRequest.getNameID() - nameId.setValue(name) - nameId.setFormat(format) - } - return logoutRequestResolver -} ----- -====== - Then, you can supply your custom `Saml2LogoutRequestResolver` in the DSL as follows: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] +[source,java] ---- http .saml2Logout((saml2) -> saml2 @@ -404,20 +159,6 @@ http ); ---- -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - saml2Logout { - logoutRequest { - logoutRequestResolver = this.logoutRequestResolver - } - } -} ----- -====== - == Customizing `` Resolution It's common to need to set other values in the `` than the defaults that Spring Security provides. @@ -431,11 +172,7 @@ By default, Spring Security will issue a `` and supply: To add other values, you can use delegation, like so: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] +[source,java] ---- @Bean public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationRepository registrations) { @@ -450,30 +187,9 @@ public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrati } ---- -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun logoutResponseResolver(registrations: RelyingPartyRegistrationRepository?): Saml2LogoutResponseResolver { - val logoutRequestResolver = OpenSaml4LogoutResponseResolver(registrations) - logoutRequestResolver.setParametersConsumer { LogoutResponseParameters parameters -> - if (checkOtherPrevailingConditions(parameters.getRequest())) { - parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT) - } - } - return logoutRequestResolver -} ----- -====== - Then, you can supply your custom `Saml2LogoutResponseResolver` in the DSL as follows: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] +[source,java] ---- http .saml2Logout((saml2) -> saml2 @@ -483,30 +199,12 @@ http ); ---- -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - saml2Logout { - logoutRequest { - logoutRequestResolver = this.logoutRequestResolver - } - } -} ----- -====== - == Customizing `` Authentication To customize validation, you can implement your own `Saml2LogoutRequestValidator`. At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutRequestValidator` like so: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] +[source,java] ---- @Component public class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator { @@ -523,66 +221,24 @@ public class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValid } ---- -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Component -open class MyOpenSamlLogoutRequestValidator: Saml2LogoutRequestValidator { - private val delegate = OpenSamlLogoutRequestValidator() - - @Override - fun logout(parameters: Saml2LogoutRequestValidatorParameters): Saml2LogoutRequestValidator { - // verify signature, issuer, destination, and principal name - val result = delegate.authenticate(authentication) - - val logoutRequest: LogoutRequest = // ... parse using OpenSAML - // perform custom validation - } -} ----- -====== - Then, you can supply your custom `Saml2LogoutRequestValidator` in the DSL as follows: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] +[source,java] ---- http .saml2Logout((saml2) -> saml2 .logoutRequest((request) -> request - .logoutRequestValidator(myOpenSamlLogoutRequestValidator) + .logoutRequestAuthenticator(myOpenSamlLogoutRequestAuthenticator) ) ); ---- -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - saml2Logout { - logoutRequest { - logoutRequestValidator = myOpenSamlLogoutRequestValidator - } - } -} ----- -====== - == Customizing `` Authentication To customize validation, you can implement your own `Saml2LogoutResponseValidator`. At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutResponseValidator` like so: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] +[source,java] ---- @Component public class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidator { @@ -599,33 +255,9 @@ public class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseVal } ---- -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Component -open class MyOpenSamlLogoutResponseValidator: Saml2LogoutResponseValidator { - private val delegate = OpenSamlLogoutResponseValidator() - - @Override - fun logout(parameters: Saml2LogoutResponseValidatorParameters): Saml2LogoutResponseValidator { - // verify signature, issuer, destination, and status - val result = delegate.authenticate(authentication) - - val logoutResponse: LogoutResponse = // ... parse using OpenSAML - // perform custom validation - } -} ----- -====== - Then, you can supply your custom `Saml2LogoutResponseValidator` in the DSL as follows: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] +[source,java] ---- http .saml2Logout((saml2) -> saml2 @@ -635,31 +267,13 @@ http ); ---- -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - saml2Logout { - logoutResponse { - logoutResponseValidator = myOpenSamlLogoutResponseValidator - } - } -} ----- -====== - == Customizing `` storage When your application sends a ``, the value is stored in the session so that the `RelayState` parameter and the `InResponseTo` attribute in the `` can be verified. If you want to store logout requests in some place other than the session, you can supply your custom implementation in the DSL, like so: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] +[source,java] ---- http .saml2Logout((saml2) -> saml2 @@ -668,24 +282,3 @@ http ) ); ---- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - saml2Logout { - logoutRequest { - logoutRequestRepository = myCustomLogoutRequestRepository - } - } -} ----- -====== - -[[jc-logout-references]] -== Further Logout-Related References - -- xref:servlet/test/mockmvc/logout.adoc#test-logout[Testing Logout] -- xref:servlet/integrations/servlet-api.adoc#servletapi-logout[HttpServletRequest.logout()] -- xref:servlet/exploits/csrf.adoc#csrf-considerations-logout[Logging Out] in section CSRF Caveats diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index c59e1b850e8..6287e5ccc1f 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -1,306 +1,30 @@ [[new]] -= What's New in Spring Security 6.3 += What's New in Spring Security 6.2 -Spring Security 6.3 provides a number of new features. -Below are the highlights of the release, or you can view https://github.com/spring-projects/spring-security/releases[the release notes] for a detailed listing of each feature and bug fix. +Spring Security 6.2 provides a number of new features. +Below are the highlights of the release. -== Passive JDK Serialization Support +== Configuration -When it comes to its support for JDK-serialized security components, Spring Security has historically been quite aggressive, supporting each serialization version for only one Spring Security minor version. -This meant that if you had JDK-serialized security components, then they would need to be evacuated before upgrading to the next Spring Security version since they would no longer be deserializable. +* https://github.com/spring-projects/spring-security/issues/5011[gh-5011] - xref:servlet/integrations/cors.adoc[(docs)] Automatically enable `.cors()` if `CorsConfigurationSource` bean is present +* https://github.com/spring-projects/spring-security/issues/13204[gh-13204] - xref:migration-7/configuration.adoc#_use_with_instead_of_apply_for_custom_dsls[(docs)] Add `AbstractConfiguredSecurityBuilder.with(...)` method to apply configurers returning the builder +* https://github.com/spring-projects/spring-security/pull/13587[gh-13587] - https://spring.io/blog/2023/08/22/tackling-the-oauth2-client-component-model-in-spring-security/[blog post] Simplify configuration of OAuth2 Client component model +* https://github.com/spring-projects/spring-security/issues/13666[gh-13666], https://github.com/spring-projects/spring-security/pull/13667[gh-13667], https://github.com/spring-projects/spring-security/issues/13726[gh-13726], https://github.com/spring-projects/spring-security/issues/13850[gh-13850] - xref:servlet/authorization/authorize-http-requests.adoc#match-by-mvc[docs] Improved CVE-2023-34035 detection -Now that Spring Security performs a minor release every six months, this became a much larger pain point. -To address that, Spring Security now will https://spring.io/blog/2024/01/19/spring-security-6-3-adds-passive-jdk-serialization-deserialization-for[maintain passivity with JDK serialization], like it does with JSON serialization, making for more seamless upgrades. +== OAuth 2.0/OIDC -== Authorization +* https://github.com/spring-projects/spring-security/issues/7845[gh-7845] - xref:reactive/oauth2/login/logout.adoc#configure-provider-initiated-oidc-logout[docs] Add OIDC Back-channel Logout Support -An ongoing theme for the last several releases has been to refactor and improve Spring Security's authorization subsystem. -Starting with replacing the `AccessDecisionManager` API with `AuthorizationManager` it's now come to the point where we are able to add several exciting new features. +== Messaging -=== Annotation Parameters - https://github.com/spring-projects/spring-security/issues/14480[#14480] +* https://github.com/spring-projects/spring-security/pull/12532[gh-12532] - Add Security Context Propagation Support -The first 6.3 feature is https://github.com/spring-projects/spring-security/issues/14480[support for annotation parameters]. -Consider Spring Security's support for xref:servlet/authorization/method-security.adoc#meta-annotations[meta-annotations] like this one: +== Web -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@PreAuthorize("hasAuthority('SCOPE_message:read')") -public @interface HasMessageRead {} ----- +* https://github.com/spring-projects/spring-security/pull/12817[gh-12817] - Make Configurable RedirectStrategy status code +* https://github.com/spring-projects/spring-security/issues/13988[gh-13988] - Make Configurable HTTP Basic request parsing -Kotlin:: -+ -.Kotlin -[source,kotlin,role="secondary"] ----- -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@PreAuthorize("hasAuthority('SCOPE_message:read')") -annotation class HasMessageRead ----- -====== +== Documentation -Before this release, something like this is only helpful when it is used widely across the codebase. -But now, xref:servlet/authorization/method-security.adoc#_templating_meta_annotation_expressions[you can add parameters] like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@PreAuthorize("hasAuthority('SCOPE_{scope}')") -public @interface HasScope { - String scope(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@PreAuthorize("hasAuthority('SCOPE_{scope}')") -annotation class HasScope (val scope:String) ----- -====== - -making it possible to do things like this: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@HasScope("message:read") -public String method() { ... } ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@HasScope("message:read") -fun method(): String { ... } ----- -====== - -and apply your SpEL expression in several more places. - -=== Secure Return Values - https://github.com/spring-projects/spring-security/issues/14596[#14596], https://github.com/spring-projects/spring-security/issues/14597[#14597] - -Since the early days of Spring Security, you've been able to xref:servlet/authorization/method-security.adoc#use-preauthorize[annotate Spring beans with `@PreAuthorize` and `@PostAuthorize`]. -But controllers, services, and repositories are not the only things you care to secure. -For example, what about a domain object `Order` where only admins should be able to call the `Order#getPayment` method? - -Now in 6.3, https://github.com/spring-projects/spring-security/issues/14597[you can annotate those methods], too. -First, annotate the `getPayment` method like you would a Spring bean: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -public class Order { - - @HasScope("payment:read") - Payment getPayment() { ... } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -class Order { - - @HasScope("payment:read") - fun getPayment(): Payment { ... } - -} ----- -====== - -And then xref:servlet/authorization/method-security.adoc#authorize-object[annotate your Spring Data repository with `@AuthorizeReturnObject`] like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -public interface OrderRepository implements CrudRepository { - - @AuthorizeReturnObject - Optional findOrderById(String id); - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- - -interface OrderRepository : CrudRepository { - @AuthorizeReturnObject - fun findOrderById(id: String?): Optional? -} ----- -====== - -At that point, Spring Security will protect any `Order` returned from `findOrderById` by way of https://github.com/spring-projects/spring-security/issues/14596[proxying the `Order` instance]. - -=== Error Handling - https://github.com/spring-projects/spring-security/issues/14598[#14598], https://github.com/spring-projects/spring-security/issues/14600[#14600], https://github.com/spring-projects/spring-security/issues/14601[#14601] - -In this release, you can also https://github.com/spring-projects/spring-security/issues/14601[intercept and handle failure at the method level] with its last new method security annotation. - -When you xref:servlet/authorization/method-security.adoc#fallback-values-authorization-denied[annotate a method with `@HandleAuthorizationDenied`] like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -public class Payment { - @HandleAuthorizationDenied(handlerClass=Mask.class) - @PreAuthorize("hasAuthority('card:read')") - public String getCreditCardNumber() { ... } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -class Payment { - @HandleAuthorizationDenied(handlerClass=Mask.class) - @PreAuthorize("hasAuthority('card:read')") - fun getCreditCardNumber(): String { ... } -} ----- -====== - -and publish a `Mask` bean: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Component -public class Mask implements MethodAuthorizationDeniedHandler { - @Override - public Object handleDeniedInvocation(MethodInvocation invocation, AuthorizationResult result) { - return "***"; - } -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Component -class Mask : MethodAuthorizationDeniedHandler { - fun handleDeniedInvocation(invocation: MethodInvocation?, result: AuthorizationResult?): Any = "***" -} ----- -====== - -then any unauthorized call to `Payment#getCreditCardNumber` will return `\***` instead of the number. - -You can see all these features at work together in https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/data[the latest Spring Security Data sample]. - -== Compromised Password Checking - https://github.com/spring-projects/spring-security/issues/7395[#7395] - -If you are going to let users pick passwords, it's critical to ensure that such a password isn't already compromised. -Spring Security 6.3 makes this as simple as xref:features/authentication/password-storage.adoc#authentication-compromised-password-check[publishing a `CompromisedPasswordChecker` bean]: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -public CompromisedPasswordChecker compromisedPasswordChecker() { - return new HaveIBeenPwnedRestApiPasswordChecker(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun compromisedPasswordChecker(): CompromisedPasswordChecker = HaveIBeenPwnedRestApiPasswordChecker() ----- -====== - -== `spring-security-rsa` is now part of Spring Security - https://github.com/spring-projects/spring-security/issues/14202[#14202] - -Since 2017, Spring Security has been undergoing a long-standing initiative to fold various Spring Security extensions into Spring Security proper. -In 6.3, `spring-security-rsa` becomes the latest of these projects which will help the team maintain and add features to it, long-term. - -`spring-security-rsa` provides a number of https://github.com/spring-projects/spring-security/blob/main/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java[handy `BytesEncryptor`] https://github.com/spring-projects/spring-security/blob/main/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java[implementations] as well as https://github.com/spring-projects/spring-security/blob/main/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java[a simpler API for working with ``KeyStore``s]. - - -== OAuth 2.0 Token Exchange Grant - https://github.com/spring-projects/spring-security/issues/5199[#5199] - -One of https://github.com/spring-projects/spring-security/issues/5199[the most highly-voted OAuth 2.0 features] in Spring Security is now in place in 6.3, which is the support for https://datatracker.ietf.org/doc/html/rfc8693#section-2[the OAuth 2.0 Token Exchange grant]. - -For xref:servlet/oauth2/client/authorization-grants.adoc#token-exchange-grant-access-token[any client configured for token exchange], you can activate it in Spring Security by adding a `TokenExchangeAuthorizedClientProvider` instance to your `OAuth2AuthorizedClientManager` like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -public OAuth2AuthorizedClientProvider tokenExchange() { - return new TokenExchangeOAuth2AuthorizedClientProvider(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun tokenExchange(): OAuth2AuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider() ----- -====== - -and then xref:servlet/oauth2/client/authorized-clients.adoc#oauth2Client-registered-authorized-client[use the `@RegisteredOAuth2AuthorizedClient` annotation] as per usual to retrieve the appropriate token with the expanded privileges your resource server needs. - -== Additional Highlights - -- https://github.com/spring-projects/spring-security/pull/14655[gh-14655] - Add `DelegatingAuthenticationConverter` -- https://github.com/spring-projects/spring-security/issues/6192[gh-6192] - Add Concurrent Sessions Control on WebFlux (xref:reactive/authentication/concurrent-sessions-control.adoc[docs]) -- https://github.com/spring-projects/spring-security/pull/14193[gh-14193] - Added support for CAS Gateway Authentication -- https://github.com/spring-projects/spring-security/issues/13259[gh-13259] - Customize when UserInfo is called -- https://github.com/spring-projects/spring-security/pull/14168[gh-14168] - Introduce Customizable AuthorizationFailureHandler in OAuth2AuthorizationRequestRedirectFilter -- https://github.com/spring-projects/spring-security/issues/14672[gh-14672] - Customize mapping the OidcUser from OidcUserRequest and OidcUserInfo -- https://github.com/spring-projects/spring-security/issues/13763[gh-13763] - Simplify configuration of reactive OAuth2 Client component model -- https://github.com/spring-projects/spring-security/issues/14758[gh-14758] - Update reactive OAuth2 docs landing page with examples (xref:reactive/oauth2/index.adoc[docs]) -- https://github.com/spring-projects/spring-security/issues/10538[gh-10538] - Support Certificate-Bound JWT Access Token Validation -- https://github.com/spring-projects/spring-security/pull/14265[gh-14265] - Support Nested username in UserInfo response -- https://github.com/spring-projects/spring-security/pull/14265[gh-14449] - Add `SecurityContext` argument resolver - -And for an exhaustive list, please see the release notes for https://github.com/spring-projects/spring-security/releases/tag/6.3.0-RC1[6.3.0-RC1], https://github.com/spring-projects/spring-security/releases/tag/6.3.0-M3[6.3.0-M3], https://github.com/spring-projects/spring-security/releases/tag/6.3.0-M2[6.3.0-M2], and https://github.com/spring-projects/spring-security/releases/tag/6.3.0-M1[6.3.0-M1]. +* https://github.com/spring-projects/spring-security/issues/13784[gh-13784] - xref:servlet/oauth2/index.adoc[docs] - Update OAuth2 docs landing page with examples +* https://github.com/spring-projects/spring-security/issues/11926[gh-11926] - xref:servlet/authentication/passwords/index.adoc#publish-authentication-manager-bean[docs] Document how to publish an `AuthenticationManager` `@Bean` without `WebSecurityConfigurerAdapter` diff --git a/git/hooks/prepare-forward-merge b/git/hooks/prepare-forward-merge index 8c07a09b2d2..19cdafe9702 100755 --- a/git/hooks/prepare-forward-merge +++ b/git/hooks/prepare-forward-merge @@ -4,7 +4,7 @@ require 'net/http' require 'yaml' require 'logger' -$main_branch = "6.3.x" +$main_branch = "6.2.x" $log = Logger.new(STDOUT) $log.level = Logger::WARN diff --git a/gradle.properties b/gradle.properties index c5d18b9d0d9..f7245966cae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ springBootVersion=3.1.1 -version=6.3.0-SNAPSHOT -samplesBranch=main +version=6.2.5-SNAPSHOT +samplesBranch=6.2.x org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true org.gradle.caching=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8c617d6a9aa..1d19b0663a3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,19 +5,19 @@ io-spring-javaformat = "0.0.41" io-spring-nohttp = "0.0.11" jakarta-websocket = "2.1.1" org-apache-directory-server = "1.5.5" -org-apache-maven-resolver = "1.9.20" +org-apache-maven-resolver = "1.9.19" org-aspectj = "1.9.22" -org-bouncycastle = "1.78.1" +org-bouncycastle = "1.70" org-eclipse-jetty = "11.0.20" org-jetbrains-kotlin = "1.9.24" -org-jetbrains-kotlinx = "1.8.0" -org-mockito = "5.11.0" +org-jetbrains-kotlinx = "1.7.3" +org-mockito = "5.5.0" org-opensaml = "4.3.2" org-springframework = "6.1.6" [libraries] -ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.6" -com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.17.1" +ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.4.14" +com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.15.4" com-google-inject-guice = "com.google.inject:guice:3.0" com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0" com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.37.3" @@ -58,16 +58,16 @@ org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apach org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" } org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" } org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4.0.4" -io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.6" +io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.4" org-aspectj-aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "org-aspectj" } org-aspectj-aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "org-aspectj" } -org-assertj-assertj-core = "org.assertj:assertj-core:3.25.3" -org-bouncycastle-bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "org-bouncycastle" } -org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "org-bouncycastle" } +org-assertj-assertj-core = "org.assertj:assertj-core:3.24.2" +org-bouncycastle-bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "org-bouncycastle" } +org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "org-bouncycastle" } org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" } org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" } org-hamcrest = "org.hamcrest:hamcrest:2.2" -org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.4.8.Final" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.3.2.Final" org-hsqldb = "org.hsqldb:hsqldb:2.7.2" org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" } org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24" @@ -84,23 +84,23 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.1" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.13" -org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.0.0-RC1" +org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2023.1.5" org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.3" org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" } org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0" -com-google-code-gson-gson = "com.google.code.gson:gson:2.10.1" +com-google-code-gson-gson = "com.google.code.gson:gson:2.8.9" com-thaiopensource-trag = "com.thaiopensource:trang:20091111" net-sourceforge-saxon-saxon = "net.sourceforge.saxon:saxon:9.1.0.8" -org-yaml-snakeyaml = "org.yaml:snakeyaml:1.33" +org-yaml-snakeyaml = "org.yaml:snakeyaml:1.30" org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2" -io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.3.0" -org-gretty-gretty = "org.gretty:gretty:4.1.3" -com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.51.0" -com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.13" +io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.1.0" +org-gretty-gretty = "org.gretty:gretty:4.0.3" +com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.38.0" +com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.11" 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:4.33.15" -org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8.0.1969" +org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.29.4" +org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1" org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1" [plugins] diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManagerTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManagerTests.java index 53ec07d3353..63d7bba4eac 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManagerTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManagerTests.java @@ -49,7 +49,6 @@ /** * @author Luke Taylor * @author Eddú Meléndez - * @author Roman Zabaluev */ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = ApacheDsContainerConfig.class) @@ -61,8 +60,6 @@ public class LdapUserDetailsManagerTests { private static final List TEST_AUTHORITIES = AuthorityUtils.createAuthorityList("ROLE_CLOWNS", "ROLE_ACROBATS"); - private static final String DEFAULT_ROLE_PREFIX = "ROLE_"; - private LdapUserDetailsManager mgr; private SpringSecurityLdapTemplate template; @@ -251,35 +248,4 @@ public void testPasswordChangeWithWrongOldPasswordFails() { .isThrownBy(() -> this.mgr.changePassword("wrongpassword", "yossariansnewpassword")); } - @Test - public void testRoleNamesStartWithDefaultRolePrefix() { - this.mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people", "uid")); - this.mgr.setGroupSearchBase("ou=groups"); - LdapUserDetails bob = (LdapUserDetails) this.mgr.loadUserByUsername("bob"); - - assertThat(bob.getAuthorities()).isNotEmpty(); - - bob.getAuthorities() - .stream() - .map(GrantedAuthority::getAuthority) - .forEach((authority) -> assertThat(authority).startsWith(DEFAULT_ROLE_PREFIX)); - } - - @Test - public void testRoleNamesStartWithCustomRolePrefix() { - var customPrefix = "GROUP_"; - this.mgr.setRolePrefix(customPrefix); - - this.mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people", "uid")); - this.mgr.setGroupSearchBase("ou=groups"); - LdapUserDetails bob = (LdapUserDetails) this.mgr.loadUserByUsername("bob"); - - assertThat(bob.getAuthorities()).isNotEmpty(); - - bob.getAuthorities() - .stream() - .map(GrantedAuthority::getAuthority) - .forEach((authority) -> assertThat(authority).startsWith(customPrefix)); - } - } diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java index c34f17f1a8e..d04e13d7b49 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,12 @@ package org.springframework.security.ldap.authentication.ad; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -36,6 +39,7 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.ldap.CommunicationException; import org.springframework.ldap.core.DirContextOperations; +import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.support.DefaultDirObjectFactory; import org.springframework.ldap.support.LdapUtils; import org.springframework.security.authentication.AccountExpiredException; @@ -46,10 +50,11 @@ import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.ldap.SpringSecurityLdapTemplate; import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider; -import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -67,9 +72,9 @@ *

* The user authorities are obtained from the data contained in the {@code memberOf} * attribute. - *

+ * *

Active Directory Sub-Error Codes

- *

+ * * When an authentication fails, resulting in a standard LDAP 49 error code, Active * Directory also supplies its own sub-error codes within the error message. These will be * used to provide additional log information on why an authentication has failed. Typical @@ -85,14 +90,13 @@ *

  • 773 - user must reset password
  • *
  • 775 - account locked
  • * - *

    + * * If you set the {@link #setConvertSubErrorCodesToExceptions(boolean) * convertSubErrorCodesToExceptions} property to {@code true}, the codes will also be used * to control the exception raised. * * @author Luke Taylor * @author Rob Winch - * @author Roman Zabaluev * @since 3.1 */ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider { @@ -131,12 +135,10 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda // Only used to allow tests to substitute a mock LdapContext ContextFactory contextFactory = new ContextFactory(); - private LdapAuthoritiesPopulator authoritiesPopulator = new DefaultActiveDirectoryAuthoritiesPopulator(); - /** - * @param domain the domain name (can be null or empty) + * @param domain the domain name (may be null or empty) * @param url an LDAP url (or multiple URLs) - * @param rootDn the root DN (can be null or empty) + * @param rootDn the root DN (may be null or empty) */ public ActiveDirectoryLdapAuthenticationProvider(String domain, String url, String rootDn) { Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty"); @@ -146,7 +148,7 @@ public ActiveDirectoryLdapAuthenticationProvider(String domain, String url, Stri } /** - * @param domain the domain name (can be null or empty) + * @param domain the domain name (may be null or empty) * @param url an LDAP url (or multiple URLs) */ public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) { @@ -184,7 +186,19 @@ protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationTo @Override protected Collection loadUserAuthorities(DirContextOperations userData, String username, String password) { - return this.authoritiesPopulator.getGrantedAuthorities(userData, username); + String[] groups = userData.getStringAttributes("memberOf"); + if (groups == null) { + this.logger.debug("No values for 'memberOf' attribute."); + return AuthorityUtils.NO_AUTHORITIES; + } + if (this.logger.isDebugEnabled()) { + this.logger.debug("'memberOf' attribute values: " + Arrays.asList(groups)); + } + List authorities = new ArrayList<>(groups.length); + for (String group : groups) { + authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue())); + } + return authorities; } private DirContext bindAsUser(String username, String password) { @@ -318,14 +332,14 @@ private String searchRootFromPrincipal(String bindPrincipal) { + "' does not contain the domain, and no domain has been configured"); throw badCredentials(); } - return rootDnFromDomain(bindPrincipal.substring(atChar + 1)); + return rootDnFromDomain(bindPrincipal.substring(atChar + 1, bindPrincipal.length())); } private String rootDnFromDomain(String domain) { String[] tokens = StringUtils.tokenizeToStringArray(domain, "."); StringBuilder root = new StringBuilder(); for (String token : tokens) { - if (!root.isEmpty()) { + if (root.length() > 0) { root.append(','); } root.append("dc=").append(token); @@ -365,6 +379,7 @@ public void setConvertSubErrorCodesToExceptions(boolean convertSubErrorCodesToEx * Defaults to: {@code (&(objectClass=user)(userPrincipalName={0}))} *

    * @param searchFilter the filter string + * * @since 3.2.6 */ public void setSearchFilter(String searchFilter) { @@ -382,19 +397,6 @@ public void setContextEnvironmentProperties(Map environment) { this.contextEnvironmentProperties = new Hashtable<>(environment); } - /** - * Set the strategy for obtaining the authorities for a given user after they've been - * authenticated. Consider adjusting this if you require a custom authorities mapping - * algorithm different from a default one. The default value is - * DefaultActiveDirectoryAuthoritiesPopulator. - * @param authoritiesPopulator authorities population strategy - * @since 6.3 - */ - public void setAuthoritiesPopulator(LdapAuthoritiesPopulator authoritiesPopulator) { - Assert.notNull(authoritiesPopulator, "authoritiesPopulator must not be null"); - this.authoritiesPopulator = authoritiesPopulator; - } - static class ContextFactory { DirContext createContext(Hashtable env) throws NamingException { diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/DefaultActiveDirectoryAuthoritiesPopulator.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/DefaultActiveDirectoryAuthoritiesPopulator.java deleted file mode 100644 index 1f51b4de10d..00000000000 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/DefaultActiveDirectoryAuthoritiesPopulator.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.ldap.authentication.ad; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.ldap.core.DirContextOperations; -import org.springframework.ldap.core.DistinguishedName; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; - -/** - * The default strategy for obtaining user role information from the active directory. - * Creates the user authority list from the values of the {@code memberOf} attribute - * obtained from the user's Active Directory entry. - * - * @author Luke Taylor - * @author Roman Zabaluev - * @since 6.3 - */ -public final class DefaultActiveDirectoryAuthoritiesPopulator implements LdapAuthoritiesPopulator { - - private final Log logger = LogFactory.getLog(getClass()); - - @Override - public Collection getGrantedAuthorities(DirContextOperations userData, - String username) { - String[] groups = userData.getStringAttributes("memberOf"); - if (groups == null) { - this.logger.debug("No values for 'memberOf' attribute."); - return AuthorityUtils.NO_AUTHORITIES; - } - if (this.logger.isDebugEnabled()) { - this.logger.debug("'memberOf' attribute values: " + Arrays.asList(groups)); - } - - List authorities = new ArrayList<>(groups.length); - - for (String group : groups) { - authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue())); - } - - return authorities; - } - -} diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java index cb3d4c3ff08..a1487b66653 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -104,7 +104,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { /** The attribute which contains members of a group */ private String groupMemberAttributeName = "uniquemember"; - private String rolePrefix = "ROLE_"; + private final String rolePrefix = "ROLE_"; /** The pattern to be used for the user search. {0} is the user's DN */ private String groupSearchFilter = "(uniquemember={0})"; @@ -403,16 +403,6 @@ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy secur this.securityContextHolderStrategy = securityContextHolderStrategy; } - /** - * Sets the role prefix used when converting authorities. The default value is "ROLE_" - * @param rolePrefix role prefix - * @since 6.3 - */ - public void setRolePrefix(String rolePrefix) { - Assert.notNull(rolePrefix, "A rolePrefix must be supplied"); - this.rolePrefix = rolePrefix; - } - private void changePasswordUsingAttributeModification(DistinguishedName userDn, String oldPassword, String newPassword) { ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(DirContext.REPLACE_ATTRIBUTE, diff --git a/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java b/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java index ec69e1d389f..3b67e85a437 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java +++ b/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java @@ -118,21 +118,7 @@ public void setAdapterRegistry(ReactiveAdapterRegistry adapterRegistry) { @Override public boolean supportsParameter(MethodParameter parameter) { - return isMonoSecurityContext(parameter) - || findMethodAnnotation(CurrentSecurityContext.class, parameter) != null; - } - - private boolean isMonoSecurityContext(MethodParameter parameter) { - boolean isParameterPublisher = Publisher.class.isAssignableFrom(parameter.getParameterType()); - if (isParameterPublisher) { - ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); - Class genericType = resolvableType.resolveGeneric(0); - if (genericType == null) { - return false; - } - return SecurityContext.class.isAssignableFrom(genericType); - } - return false; + return findMethodAnnotation(CurrentSecurityContext.class, parameter) != null; } @Override @@ -150,14 +136,6 @@ public Mono resolveArgument(MethodParameter parameter, Message messag private Object resolveSecurityContext(MethodParameter parameter, Object securityContext) { CurrentSecurityContext contextAnno = findMethodAnnotation(CurrentSecurityContext.class, parameter); - if (contextAnno != null) { - return resolveSecurityContextFromAnnotation(contextAnno, parameter, securityContext); - } - return securityContext; - } - - private Object resolveSecurityContextFromAnnotation(CurrentSecurityContext contextAnno, MethodParameter parameter, - Object securityContext) { String expressionToParse = contextAnno.expression(); if (StringUtils.hasLength(expressionToParse)) { StandardEvaluationContext context = new StandardEvaluationContext(); diff --git a/messaging/src/test/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolverTests.java b/messaging/src/test/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolverTests.java index 22876bde63e..9b715b65451 100644 --- a/messaging/src/test/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolverTests.java +++ b/messaging/src/test/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolverTests.java @@ -46,24 +46,6 @@ public void supportsParameterWhenAuthenticationPrincipalThenTrue() { assertThat(this.resolver.supportsParameter(arg0("currentSecurityContextOnMonoSecurityContext"))).isTrue(); } - @Test - public void supportsParameterWhenMonoSecurityContextNoAnnotationThenTrue() { - assertThat(this.resolver.supportsParameter(arg0("currentSecurityContextOnMonoSecurityContextNoAnnotation"))) - .isTrue(); - } - - @Test - public void supportsParameterWhenMonoCustomSecurityContextNoAnnotationThenTrue() { - assertThat( - this.resolver.supportsParameter(arg0("currentCustomSecurityContextOnMonoSecurityContextNoAnnotation"))) - .isTrue(); - } - - @Test - public void supportsParameterWhenNoSecurityContextNoAnnotationThenFalse() { - assertThat(this.resolver.supportsParameter(arg0("currentSecurityContextOnMonoStringNoAnnotation"))).isFalse(); - } - @Test public void resolveArgumentWhenAuthenticationPrincipalAndEmptyContextThenNull() { Object result = this.resolver.resolveArgument(arg0("currentSecurityContextOnMonoSecurityContext"), null) @@ -85,18 +67,6 @@ public void resolveArgumentWhenAuthenticationPrincipalThenFound() { private void currentSecurityContextOnMonoSecurityContext(@CurrentSecurityContext Mono context) { } - @SuppressWarnings("unused") - private void currentSecurityContextOnMonoSecurityContextNoAnnotation(Mono context) { - } - - @SuppressWarnings("unused") - private void currentCustomSecurityContextOnMonoSecurityContextNoAnnotation(Mono context) { - } - - @SuppressWarnings("unused") - private void currentSecurityContextOnMonoStringNoAnnotation(Mono context) { - } - @Test public void supportsParameterWhenCurrentUserThenTrue() { assertThat(this.resolver.supportsParameter(arg0("currentUserOnMonoUserDetails"))).isTrue(); @@ -140,41 +110,6 @@ public void supportsParameterWhenNotAnnotatedThenFalse() { private void monoUserDetails(Mono user) { } - @Test - public void supportsParameterWhenSecurityContextNotAnnotatedThenTrue() { - assertThat(this.resolver.supportsParameter(arg0("monoSecurityContext"))).isTrue(); - } - - @Test - public void resolveArgumentWhenMonoSecurityContextNoAnnotationThenFound() { - Authentication authentication = TestAuthentication.authenticatedUser(); - Mono result = (Mono) this.resolver - .resolveArgument(arg0("monoSecurityContext"), null) - .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication)) - .block(); - assertThat(result.block().getAuthentication().getPrincipal()).isEqualTo(authentication.getPrincipal()); - } - - @SuppressWarnings("unused") - private void monoSecurityContext(Mono securityContext) { - } - - @Test - public void resolveArgumentWhenMonoCustomSecurityContextNoAnnotationThenFound() { - Authentication authentication = TestAuthentication.authenticatedUser(); - CustomSecurityContext securityContext = new CustomSecurityContext(); - securityContext.setAuthentication(authentication); - Mono result = (Mono) this.resolver - .resolveArgument(arg0("monoCustomSecurityContext"), null) - .contextWrite(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext))) - .block(); - assertThat(result.block().getAuthentication().getPrincipal()).isEqualTo(authentication.getPrincipal()); - } - - @SuppressWarnings("unused") - private void monoCustomSecurityContext(Mono securityContext) { - } - private MethodParameter arg0(String methodName) { ResolvableMethod method = ResolvableMethod.on(getClass()).named(methodName).method(); return new SynthesizingMethodParameter(method.method(), 0); @@ -186,20 +121,4 @@ private MethodParameter arg0(String methodName) { } - static class CustomSecurityContext implements SecurityContext { - - private Authentication authentication; - - @Override - public Authentication getAuthentication() { - return this.authentication; - } - - @Override - public void setAuthentication(Authentication authentication) { - this.authentication = authentication; - } - - } - } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientId.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientId.java index c0959fbd20c..4662679fd08 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientId.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientId.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import java.util.Objects; import org.springframework.security.core.SpringSecurityCoreVersion; -import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.util.Assert; /** @@ -51,24 +50,6 @@ public OAuth2AuthorizedClientId(String clientRegistrationId, String principalNam this.principalName = principalName; } - /** - * Returns the identifier for the {@link ClientRegistration client registration}. - * @return the identifier for the client registration - * @since 6.3 - */ - public String getClientRegistrationId() { - return this.clientRegistrationId; - } - - /** - * Returns the name of the End-User {@code Principal} (Resource Owner). - * @return the name of the End-User - * @since 6.3 - */ - public String getPrincipalName() { - return this.principalName; - } - @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java deleted file mode 100644 index 256ced675ab..00000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client; - -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.util.function.Function; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.client.endpoint.DefaultTokenExchangeTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.util.Assert; - -/** - * An implementation of an {@link OAuth2AuthorizedClientProvider} for the - * {@link AuthorizationGrantType#TOKEN_EXCHANGE token-exchange} grant. - * - * @author Steve Riesenberg - * @since 6.3 - * @see OAuth2AuthorizedClientProvider - * @see DefaultTokenExchangeTokenResponseClient - */ -public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider { - - private OAuth2AccessTokenResponseClient accessTokenResponseClient = new DefaultTokenExchangeTokenResponseClient(); - - private Function subjectTokenResolver = this::resolveSubjectToken; - - private Function actorTokenResolver = (context) -> null; - - private Duration clockSkew = Duration.ofSeconds(60); - - private Clock clock = Clock.systemUTC(); - - /** - * Attempt to authorize (or re-authorize) the - * {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided - * {@code context}. Returns {@code null} if authorization (or re-authorization) is not - * supported, e.g. the client's {@link ClientRegistration#getAuthorizationGrantType() - * authorization grant type} is not {@link AuthorizationGrantType#TOKEN_EXCHANGE - * token-exchange} OR the {@link OAuth2AuthorizedClient#getAccessToken() access token} - * is not expired. - * @param context the context that holds authorization-specific state for the client - * @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization is not - * supported - */ - @Override - @Nullable - public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) { - Assert.notNull(context, "context cannot be null"); - ClientRegistration clientRegistration = context.getClientRegistration(); - if (!AuthorizationGrantType.TOKEN_EXCHANGE.equals(clientRegistration.getAuthorizationGrantType())) { - return null; - } - OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient(); - if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) { - // If client is already authorized but access token is NOT expired than no - // need for re-authorization - return null; - } - OAuth2Token subjectToken = this.subjectTokenResolver.apply(context); - if (subjectToken == null) { - return null; - } - - OAuth2Token actorToken = this.actorTokenResolver.apply(context); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, subjectToken, - actorToken); - OAuth2AccessTokenResponse tokenResponse = getTokenResponse(clientRegistration, grantRequest); - - return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(), - tokenResponse.getAccessToken()); - } - - private OAuth2Token resolveSubjectToken(OAuth2AuthorizationContext context) { - if (context.getPrincipal().getPrincipal() instanceof OAuth2Token accessToken) { - return accessToken; - } - return null; - } - - private OAuth2AccessTokenResponse getTokenResponse(ClientRegistration clientRegistration, - TokenExchangeGrantRequest tokenExchangeGrantRequest) { - try { - return this.accessTokenResponseClient.getTokenResponse(tokenExchangeGrantRequest); - } - catch (OAuth2AuthorizationException ex) { - throw new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex); - } - } - - private boolean hasTokenExpired(OAuth2Token token) { - return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew)); - } - - /** - * Sets the client used when requesting an access token credential at the Token - * Endpoint for the {@code token-exchange} grant. - * @param accessTokenResponseClient the client used when requesting an access token - * credential at the Token Endpoint for the {@code token-exchange} grant - */ - public void setAccessTokenResponseClient( - OAuth2AccessTokenResponseClient accessTokenResponseClient) { - Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null"); - this.accessTokenResponseClient = accessTokenResponseClient; - } - - /** - * Sets the resolver used for resolving the {@link OAuth2Token subject token}. - * @param subjectTokenResolver the resolver used for resolving the {@link OAuth2Token - * subject token} - */ - public void setSubjectTokenResolver(Function subjectTokenResolver) { - Assert.notNull(subjectTokenResolver, "subjectTokenResolver cannot be null"); - this.subjectTokenResolver = subjectTokenResolver; - } - - /** - * Sets the resolver used for resolving the {@link OAuth2Token actor token}. - * @param actorTokenResolver the resolver used for resolving the {@link OAuth2Token - * actor token} - */ - public void setActorTokenResolver(Function actorTokenResolver) { - Assert.notNull(actorTokenResolver, "actorTokenResolver cannot be null"); - this.actorTokenResolver = actorTokenResolver; - } - - /** - * Sets the maximum acceptable clock skew, which is used when checking the - * {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is - * 60 seconds. - * - *

    - * An access token is considered expired if - * {@code OAuth2AccessToken#getExpiresAt() - clockSkew} is before the current time - * {@code clock#instant()}. - * @param clockSkew the maximum acceptable clock skew - */ - public void setClockSkew(Duration clockSkew) { - Assert.notNull(clockSkew, "clockSkew cannot be null"); - Assert.isTrue(clockSkew.getSeconds() >= 0, "clockSkew must be >= 0"); - this.clockSkew = clockSkew; - } - - /** - * Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access - * token expiry. - * @param clock the clock - */ - public void setClock(Clock clock) { - Assert.notNull(clock, "clock cannot be null"); - this.clock = clock; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeReactiveOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeReactiveOAuth2AuthorizedClientProvider.java deleted file mode 100644 index 43e0607d2ea..00000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeReactiveOAuth2AuthorizedClientProvider.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client; - -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.util.function.Function; - -import reactor.core.publisher.Mono; - -import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; -import org.springframework.security.oauth2.client.endpoint.WebClientReactiveTokenExchangeTokenResponseClient; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.util.Assert; - -/** - * An implementation of an {@link ReactiveOAuth2AuthorizedClientProvider} for the - * {@link AuthorizationGrantType#TOKEN_EXCHANGE token-exchange} grant. - * - * @author Steve Riesenberg - * @since 6.3 - * @see ReactiveOAuth2AuthorizedClientProvider - * @see WebClientReactiveTokenExchangeTokenResponseClient - */ -public final class TokenExchangeReactiveOAuth2AuthorizedClientProvider - implements ReactiveOAuth2AuthorizedClientProvider { - - private ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = new WebClientReactiveTokenExchangeTokenResponseClient(); - - private Function> subjectTokenResolver = this::resolveSubjectToken; - - private Function> actorTokenResolver = (context) -> Mono.empty(); - - private Duration clockSkew = Duration.ofSeconds(60); - - private Clock clock = Clock.systemUTC(); - - /** - * Attempt to authorize (or re-authorize) the - * {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided - * {@code context}. Returns an empty {@code Mono} if authorization (or - * re-authorization) is not supported, e.g. the client's - * {@link ClientRegistration#getAuthorizationGrantType() authorization grant type} is - * not {@link AuthorizationGrantType#TOKEN_EXCHANGE token-exchange} OR the - * {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired. - * @param context the context that holds authorization-specific state for the client - * @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if - * authorization is not supported - */ - @Override - public Mono authorize(OAuth2AuthorizationContext context) { - Assert.notNull(context, "context cannot be null"); - ClientRegistration clientRegistration = context.getClientRegistration(); - if (!AuthorizationGrantType.TOKEN_EXCHANGE.equals(clientRegistration.getAuthorizationGrantType())) { - return Mono.empty(); - } - OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient(); - if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) { - // If client is already authorized but access token is NOT expired than no - // need for re-authorization - return Mono.empty(); - } - - return this.subjectTokenResolver.apply(context) - .flatMap((subjectToken) -> this.actorTokenResolver.apply(context) - .map((actorToken) -> new TokenExchangeGrantRequest(clientRegistration, subjectToken, actorToken)) - .defaultIfEmpty(new TokenExchangeGrantRequest(clientRegistration, subjectToken, null))) - .flatMap(this.accessTokenResponseClient::getTokenResponse) - .onErrorMap(OAuth2AuthorizationException.class, - (ex) -> new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex)) - .map((tokenResponse) -> new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(), - tokenResponse.getAccessToken())); - } - - private Mono resolveSubjectToken(OAuth2AuthorizationContext context) { - // @formatter:off - return Mono.just(context) - .map((ctx) -> ctx.getPrincipal().getPrincipal()) - .filter((principal) -> principal instanceof OAuth2Token) - .cast(OAuth2Token.class); - // @formatter:on - } - - private boolean hasTokenExpired(OAuth2Token token) { - return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew)); - } - - /** - * Sets the client used when requesting an access token credential at the Token - * Endpoint for the {@code token-exchange} grant. - * @param accessTokenResponseClient the client used when requesting an access token - * credential at the Token Endpoint for the {@code token-exchange} grant - */ - public void setAccessTokenResponseClient( - ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient) { - Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null"); - this.accessTokenResponseClient = accessTokenResponseClient; - } - - /** - * Sets the resolver used for resolving the {@link OAuth2Token subject token}. - * @param subjectTokenResolver the resolver used for resolving the {@link OAuth2Token - * subject token} - */ - public void setSubjectTokenResolver(Function> subjectTokenResolver) { - Assert.notNull(subjectTokenResolver, "subjectTokenResolver cannot be null"); - this.subjectTokenResolver = subjectTokenResolver; - } - - /** - * Sets the resolver used for resolving the {@link OAuth2Token actor token}. - * @param actorTokenResolver the resolver used for resolving the {@link OAuth2Token - * actor token} - */ - public void setActorTokenResolver(Function> actorTokenResolver) { - Assert.notNull(actorTokenResolver, "actorTokenResolver cannot be null"); - this.actorTokenResolver = actorTokenResolver; - } - - /** - * Sets the maximum acceptable clock skew, which is used when checking the - * {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is - * 60 seconds. - * - *

    - * An access token is considered expired if - * {@code OAuth2AccessToken#getExpiresAt() - clockSkew} is before the current time - * {@code clock#instant()}. - * @param clockSkew the maximum acceptable clock skew - */ - public void setClockSkew(Duration clockSkew) { - Assert.notNull(clockSkew, "clockSkew cannot be null"); - Assert.isTrue(clockSkew.getSeconds() >= 0, "clockSkew must be >= 0"); - this.clockSkew = clockSkew; - } - - /** - * Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access - * token expiry. - * @param clock the clock - */ - public void setClock(Clock clock) { - Assert.notNull(clock, "clock cannot be null"); - this.clock = clock; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClient.java deleted file mode 100644 index 787e72ad877..00000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClient.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client.endpoint; - -import java.util.Arrays; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.FormHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.util.Assert; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; - -/** - * The default implementation of an {@link OAuth2AccessTokenResponseClient} for the - * {@link AuthorizationGrantType#TOKEN_EXCHANGE token-exchange} grant. This implementation - * uses a {@link RestOperations} when requesting an access token credential at the - * Authorization Server's Token Endpoint. - * - * @author Steve Riesenberg - * @since 6.3 - * @see OAuth2AccessTokenResponseClient - * @see TokenExchangeGrantRequest - * @see OAuth2AccessTokenResponse - * @see Section - * 2.1 Request - * @see Section - * 2.2 Response - */ -public final class DefaultTokenExchangeTokenResponseClient - implements OAuth2AccessTokenResponseClient { - - private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; - - private Converter> requestEntityConverter = new ClientAuthenticationMethodValidatingRequestEntityConverter<>( - new TokenExchangeGrantRequestEntityConverter()); - - private RestOperations restOperations; - - public DefaultTokenExchangeTokenResponseClient() { - RestTemplate restTemplate = new RestTemplate( - Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - this.restOperations = restTemplate; - } - - @Override - public OAuth2AccessTokenResponse getTokenResponse(TokenExchangeGrantRequest grantRequest) { - Assert.notNull(grantRequest, "grantRequest cannot be null"); - RequestEntity requestEntity = this.requestEntityConverter.convert(grantRequest); - ResponseEntity responseEntity = getResponse(requestEntity); - - return responseEntity.getBody(); - } - - private ResponseEntity getResponse(RequestEntity request) { - try { - return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class); - } - catch (RestClientException ex) { - OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, - "An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " - + ex.getMessage(), - null); - throw new OAuth2AuthorizationException(oauth2Error, ex); - } - } - - /** - * Sets the {@link Converter} used for converting the - * {@link TokenExchangeGrantRequest} to a {@link RequestEntity} representation of the - * OAuth 2.0 Access Token Request. - * @param requestEntityConverter the {@link Converter} used for converting to a - * {@link RequestEntity} representation of the Access Token Request - */ - public void setRequestEntityConverter( - Converter> requestEntityConverter) { - Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null"); - this.requestEntityConverter = requestEntityConverter; - } - - /** - * Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token - * Response. - * - *

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

      - *
    1. {@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and - * {@link OAuth2AccessTokenResponseHttpMessageConverter}
    2. - *
    3. {@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}
    4. - *
    - * @param restOperations the {@link RestOperations} used when requesting the Access - * Token Response - */ - public void setRestOperations(RestOperations restOperations) { - Assert.notNull(restOperations, "restOperations cannot be null"); - this.restOperations = restOperations; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java deleted file mode 100644 index 0a026a56724..00000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client.endpoint; - -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.util.Assert; - -/** - * A Token Exchange Grant request that holds the {@link OAuth2Token subject token} and - * optional {@link OAuth2Token actor token}. - * - * @author Steve Riesenberg - * @since 6.3 - * @see AbstractOAuth2AuthorizationGrantRequest - * @see ClientRegistration - * @see OAuth2Token - * @see Section - * 1.1 Delegation vs. Impersonation Semantics - * @see Section - * 2.1 Request - * @see Section - * 2.2 Response - */ -public class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantRequest { - - private final OAuth2Token subjectToken; - - private final OAuth2Token actorToken; - - /** - * Constructs a {@code TokenExchangeGrantRequest} using the provided parameters. - * @param clientRegistration the client registration - * @param subjectToken the subject token - * @param actorToken the actor token - */ - public TokenExchangeGrantRequest(ClientRegistration clientRegistration, OAuth2Token subjectToken, - OAuth2Token actorToken) { - super(AuthorizationGrantType.TOKEN_EXCHANGE, clientRegistration); - Assert.isTrue(AuthorizationGrantType.TOKEN_EXCHANGE.equals(clientRegistration.getAuthorizationGrantType()), - "clientRegistration.authorizationGrantType must be AuthorizationGrantType.TOKEN_EXCHANGE"); - Assert.notNull(subjectToken, "subjectToken cannot be null"); - this.subjectToken = subjectToken; - this.actorToken = actorToken; - } - - /** - * Returns the {@link OAuth2Token subject token}. - * @return the {@link OAuth2Token subject token} - */ - public OAuth2Token getSubjectToken() { - return this.subjectToken; - } - - /** - * Returns the {@link OAuth2Token actor token}. - * @return the {@link OAuth2Token actor token} - */ - public OAuth2Token getActorToken() { - return this.actorToken; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverter.java deleted file mode 100644 index c8f72e4adb4..00000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverter.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client.endpoint; - -import org.springframework.http.RequestEntity; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.util.CollectionUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * An implementation of an {@link AbstractOAuth2AuthorizationGrantRequestEntityConverter} - * that converts the provided {@link TokenExchangeGrantRequest} to a {@link RequestEntity} - * representation of an OAuth 2.0 Access Token Request for the Token Exchange Grant. - * - * @author Steve Riesenberg - * @since 6.3 - * @see AbstractOAuth2AuthorizationGrantRequestEntityConverter - * @see TokenExchangeGrantRequest - * @see RequestEntity - * @see Section - * 1.1 Delegation vs. Impersonation Semantics - */ -public class TokenExchangeGrantRequestEntityConverter - extends AbstractOAuth2AuthorizationGrantRequestEntityConverter { - - 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"; - - @Override - protected MultiValueMap createParameters(TokenExchangeGrantRequest grantRequest) { - ClientRegistration clientRegistration = grantRequest.getClientRegistration(); - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.add(OAuth2ParameterNames.GRANT_TYPE, grantRequest.getGrantType().getValue()); - parameters.add(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - OAuth2Token subjectToken = grantRequest.getSubjectToken(); - parameters.add(OAuth2ParameterNames.SUBJECT_TOKEN, subjectToken.getTokenValue()); - parameters.add(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, tokenType(subjectToken)); - OAuth2Token actorToken = grantRequest.getActorToken(); - if (actorToken != null) { - parameters.add(OAuth2ParameterNames.ACTOR_TOKEN, actorToken.getTokenValue()); - parameters.add(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, tokenType(actorToken)); - } - if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) { - parameters.add(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); - } - if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientRegistration.getClientAuthenticationMethod())) { - parameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId()); - parameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret()); - } - return parameters; - } - - private static String tokenType(OAuth2Token token) { - return (token instanceof Jwt) ? JWT_TOKEN_TYPE_VALUE : ACCESS_TOKEN_TYPE_VALUE; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveTokenExchangeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveTokenExchangeTokenResponseClient.java deleted file mode 100644 index abc9ad751b8..00000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveTokenExchangeTokenResponseClient.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client.endpoint; - -import java.util.Set; - -import org.springframework.security.oauth2.client.registration.ClientRegistration; -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.OAuth2ParameterNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * The default implementation of an {@link ReactiveOAuth2AccessTokenResponseClient} for - * the {@link AuthorizationGrantType#TOKEN_EXCHANGE token-exchange} grant. This - * implementation uses {@link WebClient} when requesting an access token credential at the - * Authorization Server's Token Endpoint. - * - * @author Steve Riesenberg - * @since 6.3 - * @see ReactiveOAuth2AccessTokenResponseClient - * @see TokenExchangeGrantRequest - * @see OAuth2AccessToken - * @see Section - * 2.1 Request - * @see Section - * 2.2 Response - */ -public final class WebClientReactiveTokenExchangeTokenResponseClient - extends AbstractWebClientReactiveOAuth2AccessTokenResponseClient { - - 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"; - - @Override - ClientRegistration clientRegistration(TokenExchangeGrantRequest grantRequest) { - return grantRequest.getClientRegistration(); - } - - @Override - Set scopes(TokenExchangeGrantRequest grantRequest) { - return grantRequest.getClientRegistration().getScopes(); - } - - @Override - BodyInserters.FormInserter populateTokenRequestBody(TokenExchangeGrantRequest grantRequest, - BodyInserters.FormInserter body) { - super.populateTokenRequestBody(grantRequest, body); - body.with(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - OAuth2Token subjectToken = grantRequest.getSubjectToken(); - body.with(OAuth2ParameterNames.SUBJECT_TOKEN, subjectToken.getTokenValue()); - body.with(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, tokenType(subjectToken)); - OAuth2Token actorToken = grantRequest.getActorToken(); - if (actorToken != null) { - body.with(OAuth2ParameterNames.ACTOR_TOKEN, actorToken.getTokenValue()); - body.with(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, tokenType(actorToken)); - } - return body; - } - - private static String tokenType(OAuth2Token token) { - return (token instanceof Jwt) ? JWT_TOKEN_TYPE_VALUE : ACCESS_TOKEN_TYPE_VALUE; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/DefaultOidcIdTokenValidatorFactory.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/DefaultOidcIdTokenValidatorFactory.java index 3860134abc9..c1eb2858149 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/DefaultOidcIdTokenValidatorFactory.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/DefaultOidcIdTokenValidatorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,10 @@ import java.util.function.Function; import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtValidators; +import org.springframework.security.oauth2.jwt.JwtTimestampValidator; /** * @author Joe Grandja @@ -31,7 +32,8 @@ class DefaultOidcIdTokenValidatorFactory implements Function apply(ClientRegistration clientRegistration) { - return JwtValidators.createDefaultWithValidators(new OidcIdTokenValidator(clientRegistration)); + return new DelegatingOAuth2TokenValidator<>(new JwtTimestampValidator(), + new OidcIdTokenValidator(clientRegistration)); } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java index 3679b7e36e5..d00490bf235 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java @@ -76,7 +76,8 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory jwtDecoders = new ConcurrentHashMap<>(); @@ -88,17 +89,6 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory, Map>> claimTypeConverterFactory = ( clientRegistration) -> DEFAULT_CLAIM_TYPE_CONVERTER; - /** - * Returns the default {@link Converter}'s used for type conversion of claim values - * for an {@link OidcIdToken}. - * @return a {@link Map} of {@link Converter}'s keyed by {@link IdTokenClaimNames - * claim name} - * @since 6.3 - */ - public static ClaimTypeConverter createDefaultClaimTypeConverter() { - return new ClaimTypeConverter(createDefaultClaimTypeConverters()); - } - /** * Returns the default {@link Converter}'s used for type conversion of claim values * for an {@link OidcIdToken}. diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactory.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactory.java index 30039eecab4..84850bba6a2 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactory.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactory.java @@ -58,7 +58,6 @@ * @author Joe Grandja * @author Rafael Dominguez * @author Mark Heckler - * @author Ubaid ur Rehman * @since 5.2 * @see ReactiveJwtDecoderFactory * @see ClientRegistration diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserService.java index 21d2c7d0122..6a66651a44d 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,27 +18,26 @@ import java.time.Instant; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; -import java.util.function.BiFunction; +import java.util.Set; import java.util.function.Function; -import java.util.function.Predicate; import reactor.core.publisher.Mono; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService; -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.converter.ClaimConversionService; import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.OidcUserInfo; import org.springframework.security.oauth2.core.oidc.StandardClaimNames; import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; @@ -46,6 +45,7 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * An implementation of an {@link ReactiveOAuth2UserService} that supports OpenID Connect @@ -71,10 +71,6 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService< private Function, Map>> claimTypeConverterFactory = ( clientRegistration) -> DEFAULT_CLAIM_TYPE_CONVERTER; - private Predicate retrieveUserInfo = OidcUserRequestUtils::shouldRetrieveUserInfo; - - private BiFunction> oidcUserMapper = this::getUser; - /** * Returns the default {@link Converter}'s used for type conversion of claim values * for an {@link OidcUserInfo}. @@ -103,17 +99,31 @@ public Mono loadUser(OidcUserRequest userRequest) throws OAuth2Authent Assert.notNull(userRequest, "userRequest cannot be null"); // @formatter:off return getUserInfo(userRequest) - .flatMap((userInfo) -> this.oidcUserMapper.apply(userRequest, userInfo)) - .switchIfEmpty(Mono.defer(() -> this.oidcUserMapper.apply(userRequest, null))); + .map((userInfo) -> + new OidcUserAuthority(userRequest.getIdToken(), userInfo) + ) + .defaultIfEmpty(new OidcUserAuthority(userRequest.getIdToken(), null)) + .map((authority) -> { + OidcUserInfo userInfo = authority.getUserInfo(); + Set authorities = new HashSet<>(); + authorities.add(authority); + OAuth2AccessToken token = userRequest.getAccessToken(); + for (String scope : token.getScopes()) { + authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope)); + } + String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() + .getUserInfoEndpoint().getUserNameAttributeName(); + if (StringUtils.hasText(userNameAttributeName)) { + return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, + userNameAttributeName); + } + return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo); + }); // @formatter:on } - private Mono getUser(OidcUserRequest userRequest, OidcUserInfo userInfo) { - return Mono.just(OidcUserRequestUtils.getUser(userRequest, userInfo)); - } - private Mono getUserInfo(OidcUserRequest userRequest) { - if (!this.retrieveUserInfo.test(userRequest)) { + if (!OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest)) { return Mono.empty(); } // @formatter:off @@ -159,80 +169,4 @@ public final void setClaimTypeConverterFactory( this.claimTypeConverterFactory = claimTypeConverterFactory; } - /** - * Sets the {@code Predicate} used to determine if the UserInfo Endpoint should be - * called to retrieve information about the End-User (Resource Owner). - *

    - * By default, the UserInfo Endpoint is called if all of the following are true: - *

      - *
    • The user info endpoint is defined on the ClientRegistration
    • - *
    • The Client Registration uses the - * {@link AuthorizationGrantType#AUTHORIZATION_CODE} and scopes in the access token - * are defined in the {@link ClientRegistration}
    • - *
    - * @param retrieveUserInfo the function used to determine if the UserInfo Endpoint - * should be called - * @since 6.3 - */ - public final void setRetrieveUserInfo(Predicate retrieveUserInfo) { - Assert.notNull(retrieveUserInfo, "retrieveUserInfo cannot be null"); - this.retrieveUserInfo = retrieveUserInfo; - } - - /** - * Sets the {@code BiFunction} used to map the {@link OidcUser user} from the - * {@link OidcUserRequest user request} and {@link OidcUserInfo user info}. - *

    - * This is useful when you need to map the user or authorities from the access token - * itself. For example, when the authorization server provides authorization - * information in the access token payload you can do the following:

    -	 * 	@Bean
    -	 * 	public OidcReactiveOAuth2UserService oidcUserService() {
    -	 * 		var userService = new OidcReactiveOAuth2UserService();
    -	 * 		userService.setOidcUserMapper(oidcUserMapper());
    -	 * 		return userService;
    -	 * 	}
    -	 *
    -	 * 	private static BiFunction<OidcUserRequest, OidcUserInfo, Mono<OidcUser>> oidcUserMapper() {
    -	 * 		return (userRequest, userInfo) -> {
    -	 * 			var accessToken = userRequest.getAccessToken();
    -	 * 			var grantedAuthorities = new HashSet<GrantedAuthority>();
    -	 * 			// TODO: Map authorities from the access token
    -	 * 			var userNameAttributeName = "preferred_username";
    -	 * 			return Mono.just(new DefaultOidcUser(
    -	 * 				grantedAuthorities,
    -	 * 				userRequest.getIdToken(),
    -	 * 				userInfo,
    -	 * 				userNameAttributeName
    -	 * 			));
    -	 * 		};
    -	 * 	}
    -	 * 
    - *

    - * Note that you can access the {@code userNameAttributeName} via the - * {@link ClientRegistration} as follows:

    -	 * 	var userNameAttributeName = userRequest.getClientRegistration()
    -	 * 		.getProviderDetails()
    -	 * 		.getUserInfoEndpoint()
    -	 * 		.getUserNameAttributeName();
    -	 * 
    - *

    - * By default, a {@link DefaultOidcUser} is created with authorities mapped as - * follows: - *

      - *
    • An {@link OidcUserAuthority} is created from the {@link OidcIdToken} and - * {@link OidcUserInfo} with an authority of {@code OIDC_USER}
    • - *
    • Additional {@link SimpleGrantedAuthority authorities} are mapped from the - * {@link OAuth2AccessToken#getScopes() access token scopes} with a prefix of - * {@code SCOPE_}
    • - *
    - * @param oidcUserMapper the function used to map the {@link OidcUser} from the - * {@link OidcUserRequest} and {@link OidcUserInfo} - * @since 6.3 - */ - public final void setOidcUserMapper(BiFunction> oidcUserMapper) { - Assert.notNull(oidcUserMapper, "oidcUserMapper cannot be null"); - this.oidcUserMapper = oidcUserMapper; - } - } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java index 5f39b8d961b..1cd71aa072a 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java @@ -16,18 +16,8 @@ package org.springframework.security.oauth2.client.oidc.userinfo; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -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.OidcUser; -import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -76,21 +66,6 @@ static boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) { return false; } - static OidcUser getUser(OidcUserRequest userRequest, OidcUserInfo userInfo) { - Set authorities = new LinkedHashSet<>(); - authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo)); - OAuth2AccessToken token = userRequest.getAccessToken(); - for (String scope : token.getScopes()) { - authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope)); - } - ClientRegistration.ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails(); - String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName(); - if (StringUtils.hasText(userNameAttributeName)) { - return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName); - } - return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo); - } - private OidcUserRequestUtils() { } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java index a7b0151ae02..0ae4727ff7a 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,14 +20,14 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.function.BiFunction; import java.util.function.Function; -import java.util.function.Predicate; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails; @@ -40,7 +40,6 @@ import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.converter.ClaimConversionService; import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; -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; @@ -57,7 +56,6 @@ * Provider's. * * @author Joe Grandja - * @author Steve Riesenberg * @since 5.0 * @see OAuth2UserService * @see OidcUserRequest @@ -80,10 +78,6 @@ public class OidcUserService implements OAuth2UserService, Map>> claimTypeConverterFactory = ( clientRegistration) -> DEFAULT_CLAIM_TYPE_CONVERTER; - private Predicate retrieveUserInfo = this::shouldRetrieveUserInfo; - - private BiFunction oidcUserMapper = OidcUserRequestUtils::getUser; - /** * Returns the default {@link Converter}'s used for type conversion of claim values * for an {@link OidcUserInfo}. @@ -111,7 +105,7 @@ public class OidcUserService implements OAuth2UserService claims = getClaims(userRequest, oauth2User); userInfo = new OidcUserInfo(claims); @@ -133,7 +127,13 @@ public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } } - return this.oidcUserMapper.apply(userRequest, userInfo); + Set authorities = new LinkedHashSet<>(); + authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo)); + OAuth2AccessToken token = userRequest.getAccessToken(); + for (String authority : token.getScopes()) { + authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority)); + } + return getUser(userRequest, userInfo, authorities); } private Map getClaims(OidcUserRequest userRequest, OAuth2User oauth2User) { @@ -145,6 +145,15 @@ private Map getClaims(OidcUserRequest userRequest, OAuth2User oa return DEFAULT_CLAIM_TYPE_CONVERTER.convert(oauth2User.getAttributes()); } + private OidcUser getUser(OidcUserRequest userRequest, OidcUserInfo userInfo, Set authorities) { + ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails(); + String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName(); + if (StringUtils.hasText(userNameAttributeName)) { + return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName); + } + return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo); + } + private boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) { // Auto-disabled if UserInfo Endpoint URI is not provided ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails(); @@ -212,91 +221,10 @@ public final void setClaimTypeConverterFactory( * resource will be requested, otherwise it will not. * @param accessibleScopes the scope(s) that allow access to the user info resource * @since 5.2 - * @deprecated Use {@link #setRetrieveUserInfo(Predicate)} instead */ - @Deprecated(since = "6.3", forRemoval = true) public final void setAccessibleScopes(Set accessibleScopes) { Assert.notNull(accessibleScopes, "accessibleScopes cannot be null"); this.accessibleScopes = accessibleScopes; } - /** - * Sets the {@code Predicate} used to determine if the UserInfo Endpoint should be - * called to retrieve information about the End-User (Resource Owner). - *

    - * By default, the UserInfo Endpoint is called if all of the following are true: - *

      - *
    • The user info endpoint is defined on the ClientRegistration
    • - *
    • The Client Registration uses the - * {@link AuthorizationGrantType#AUTHORIZATION_CODE}
    • - *
    • The access token contains one or more scopes allowed to access the UserInfo - * Endpoint ({@link OidcScopes#PROFILE profile}, {@link OidcScopes#EMAIL email}, - * {@link OidcScopes#ADDRESS address} or {@link OidcScopes#PHONE phone}) or the access - * token scopes are empty
    • - *
    - * @param retrieveUserInfo the function used to determine if the UserInfo Endpoint - * should be called - * @since 6.3 - */ - public final void setRetrieveUserInfo(Predicate retrieveUserInfo) { - Assert.notNull(retrieveUserInfo, "retrieveUserInfo cannot be null"); - this.retrieveUserInfo = retrieveUserInfo; - } - - /** - * Sets the {@code BiFunction} used to map the {@link OidcUser user} from the - * {@link OidcUserRequest user request} and {@link OidcUserInfo user info}. - *

    - * This is useful when you need to map the user or authorities from the access token - * itself. For example, when the authorization server provides authorization - * information in the access token payload you can do the following:

    -	 * 	@Bean
    -	 * 	public OidcUserService oidcUserService() {
    -	 * 		var userService = new OidcUserService();
    -	 * 		userService.setOidcUserMapper(oidcUserMapper());
    -	 * 		return userService;
    -	 * 	}
    -	 *
    -	 * 	private static BiFunction<OidcUserRequest, OidcUserInfo, OidcUser> oidcUserMapper() {
    -	 * 		return (userRequest, userInfo) -> {
    -	 * 			var accessToken = userRequest.getAccessToken();
    -	 * 			var grantedAuthorities = new HashSet<GrantedAuthority>();
    -	 * 			// TODO: Map authorities from the access token
    -	 * 			var userNameAttributeName = "preferred_username";
    -	 * 			return new DefaultOidcUser(
    -	 * 				grantedAuthorities,
    -	 * 				userRequest.getIdToken(),
    -	 * 				userInfo,
    -	 * 				userNameAttributeName
    -	 * 			);
    -	 * 		};
    -	 * 	}
    -	 * 
    - *

    - * Note that you can access the {@code userNameAttributeName} via the - * {@link ClientRegistration} as follows:

    -	 * 	var userNameAttributeName = userRequest.getClientRegistration()
    -	 * 		.getProviderDetails()
    -	 * 		.getUserInfoEndpoint()
    -	 * 		.getUserNameAttributeName();
    -	 * 
    - *

    - * By default, a {@link DefaultOidcUser} is created with authorities mapped as - * follows: - *

      - *
    • An {@link OidcUserAuthority} is created from the {@link OidcIdToken} and - * {@link OidcUserInfo} with an authority of {@code OIDC_USER}
    • - *
    • Additional {@link SimpleGrantedAuthority authorities} are mapped from the - * {@link OAuth2AccessToken#getScopes() access token scopes} with a prefix of - * {@code SCOPE_}
    • - *
    - * @param oidcUserMapper the function used to map the {@link OidcUser} from the - * {@link OidcUserRequest} and {@link OidcUserInfo} - * @since 6.3 - */ - public final void setOidcUserMapper(BiFunction oidcUserMapper) { - Assert.notNull(oidcUserMapper, "oidcUserMapper cannot be null"); - this.oidcUserMapper = oidcUserMapper; - } - } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java index 95336084b7b..0851006de3d 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package org.springframework.security.oauth2.client.userinfo; -import java.util.Collection; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; @@ -76,9 +76,6 @@ public class DefaultOAuth2UserService implements OAuth2UserService> requestEntityConverter = new OAuth2UserRequestEntityConverter(); - private Converter, Map>> attributesConverter = ( - request) -> (attributes) -> attributes; - private RestOperations restOperations; public DefaultOAuth2UserService() { @@ -90,39 +87,35 @@ public DefaultOAuth2UserService() { @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { Assert.notNull(userRequest, "userRequest cannot be null"); - String userNameAttributeName = getUserNameAttributeName(userRequest); + if (!StringUtils + .hasText(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) { + OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_INFO_URI_ERROR_CODE, + "Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: " + + userRequest.getClientRegistration().getRegistrationId(), + null); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + String userNameAttributeName = userRequest.getClientRegistration() + .getProviderDetails() + .getUserInfoEndpoint() + .getUserNameAttributeName(); + if (!StringUtils.hasText(userNameAttributeName)) { + OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE, + "Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: " + + userRequest.getClientRegistration().getRegistrationId(), + null); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } RequestEntity request = this.requestEntityConverter.convert(userRequest); ResponseEntity> response = getResponse(userRequest, request); + Map userAttributes = response.getBody(); + Set authorities = new LinkedHashSet<>(); + authorities.add(new OAuth2UserAuthority(userAttributes)); OAuth2AccessToken token = userRequest.getAccessToken(); - Map attributes = this.attributesConverter.convert(userRequest).convert(response.getBody()); - Collection authorities = getAuthorities(token, attributes); - return new DefaultOAuth2User(authorities, attributes, userNameAttributeName); - } - - /** - * Use this strategy to adapt user attributes into a format understood by Spring - * Security; by default, the original attributes are preserved. - * - *

    - * This can be helpful, for example, if the user attribute is nested. Since Spring - * Security needs the username attribute to be at the top level, you can use this - * method to do: - * - *

    -	 *     DefaultOAuth2UserService userService = new DefaultOAuth2UserService();
    -	 *     userService.setAttributesConverter((userRequest) -> (attributes) ->
    -	 *         Map<String, Object> userObject = (Map<String, Object>) attributes.get("user");
    -	 *         attributes.put("user-name", userObject.get("user-name"));
    -	 *         return attributes;
    -	 *     });
    -	 * 
    - * @param attributesConverter the attribute adaptation strategy to use - * @since 6.3 - */ - public void setAttributesConverter( - Converter, Map>> attributesConverter) { - Assert.notNull(attributesConverter, "attributesConverter cannot be null"); - this.attributesConverter = attributesConverter; + for (String authority : token.getScopes()) { + authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority)); + } + return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName); } private ResponseEntity> getResponse(OAuth2UserRequest userRequest, RequestEntity request) { @@ -164,38 +157,6 @@ private ResponseEntity> getResponse(OAuth2UserRequest userRe } } - private String getUserNameAttributeName(OAuth2UserRequest userRequest) { - if (!StringUtils - .hasText(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) { - OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_INFO_URI_ERROR_CODE, - "Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: " - + userRequest.getClientRegistration().getRegistrationId(), - null); - throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); - } - String userNameAttributeName = userRequest.getClientRegistration() - .getProviderDetails() - .getUserInfoEndpoint() - .getUserNameAttributeName(); - if (!StringUtils.hasText(userNameAttributeName)) { - OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE, - "Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: " - + userRequest.getClientRegistration().getRegistrationId(), - null); - throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); - } - return userNameAttributeName; - } - - private Collection getAuthorities(OAuth2AccessToken token, Map attributes) { - Collection authorities = new LinkedHashSet<>(); - authorities.add(new OAuth2UserAuthority(attributes)); - for (String authority : token.getScopes()) { - authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority)); - } - return authorities; - } - /** * Sets the {@link Converter} used for converting the {@link OAuth2UserRequest} to a * {@link RequestEntity} representation of the UserInfo Request. diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java index 920119baab3..90c33fd41b1 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -26,7 +26,6 @@ import reactor.core.publisher.Mono; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; @@ -79,9 +78,6 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi private static final ParameterizedTypeReference> STRING_STRING_MAP = new ParameterizedTypeReference>() { }; - private Converter, Map>> attributesConverter = ( - request) -> (attributes) -> attributes; - private WebClient webClient = WebClient.create(); @Override @@ -127,8 +123,7 @@ public Mono loadUser(OAuth2UserRequest userRequest) throws OAuth2Aut throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); }) ) - .bodyToMono(DefaultReactiveOAuth2UserService.STRING_OBJECT_MAP) - .mapNotNull((attributes) -> this.attributesConverter.convert(userRequest).convert(attributes)); + .bodyToMono(DefaultReactiveOAuth2UserService.STRING_OBJECT_MAP); return userAttributes.map((attrs) -> { GrantedAuthority authority = new OAuth2UserAuthority(attrs); Set authorities = new HashSet<>(); @@ -189,32 +184,6 @@ private WebClient.RequestHeadersSpec getRequestHeaderSpec(OAuth2UserRequest u // @formatter:on } - /** - * Use this strategy to adapt user attributes into a format understood by Spring - * Security; by default, the original attributes are preserved. - * - *

    - * This can be helpful, for example, if the user attribute is nested. Since Spring - * Security needs the username attribute to be at the top level, you can use this - * method to do: - * - *

    -	 *     DefaultReactiveOAuth2UserService userService = new DefaultReactiveOAuth2UserService();
    -	 *     userService.setAttributesConverter((userRequest) -> (attributes) ->
    -	 *         Map<String, Object> userObject = (Map<String, Object>) attributes.get("user");
    -	 *         attributes.put("user-name", userObject.get("user-name"));
    -	 *         return attributes;
    -	 *     });
    -	 * 
    - * @param attributesConverter the attribute adaptation strategy to use - * @since 6.3 - */ - public void setAttributesConverter( - Converter, Map>> attributesConverter) { - Assert.notNull(attributesConverter, "attributesConverter cannot be null"); - this.attributesConverter = attributesConverter; - } - /** * Sets the {@link WebClient} used for retrieving the user endpoint * @param webClient the client to use diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilter.java index 65d0be31233..2923f05bf0f 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -25,7 +25,6 @@ import org.springframework.core.log.LogMessage; import org.springframework.http.HttpStatus; -import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; @@ -33,7 +32,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.util.ThrowableAnalyzer; @@ -99,8 +97,6 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt private RequestCache requestCache = new HttpSessionRequestCache(); - private AuthenticationFailureHandler authenticationFailureHandler = this::unsuccessfulRedirectForAuthorization; - /** * Constructs an {@code OAuth2AuthorizationRequestRedirectFilter} using the provided * parameters. @@ -167,18 +163,6 @@ public final void setRequestCache(RequestCache requestCache) { this.requestCache = requestCache; } - /** - * Sets the {@link AuthenticationFailureHandler} used to handle errors redirecting to - * the Authorization Server's Authorization Endpoint. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * to handle errors redirecting to the Authorization Server's Authorization Endpoint - * @since 6.3 - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { @@ -190,8 +174,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } } catch (Exception ex) { - AuthenticationException wrappedException = new OAuth2AuthorizationRequestException(ex); - this.authenticationFailureHandler.onAuthenticationFailure(request, response, wrappedException); + this.unsuccessfulRedirectForAuthorization(request, response, ex); return; } try { @@ -216,8 +199,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse this.sendRedirectForAuthorization(request, response, authorizationRequest); } catch (Exception failed) { - AuthenticationException wrappedException = new OAuth2AuthorizationRequestException(ex); - this.authenticationFailureHandler.onAuthenticationFailure(request, response, wrappedException); + this.unsuccessfulRedirectForAuthorization(request, response, failed); } return; } @@ -241,10 +223,9 @@ private void sendRedirectForAuthorization(HttpServletRequest request, HttpServle } private void unsuccessfulRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response, - AuthenticationException ex) throws IOException { - Throwable cause = ex.getCause(); - LogMessage message = LogMessage.format("Authorization Request failed: %s", cause); - if (InvalidClientRegistrationIdException.class.isAssignableFrom(cause.getClass())) { + Exception ex) throws IOException { + LogMessage message = LogMessage.format("Authorization Request failed: %s", ex); + if (InvalidClientRegistrationIdException.class.isAssignableFrom(ex.getClass())) { // Log an invalid registrationId at WARN level to allow these errors to be // tuned separately from other errors this.logger.warn(message, ex); @@ -269,12 +250,4 @@ protected void initExtractorMap() { } - private static final class OAuth2AuthorizationRequestException extends AuthenticationException { - - OAuth2AuthorizationRequestException(Throwable cause) { - super(cause.getMessage(), cause); - } - - } - } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProviderTests.java deleted file mode 100644 index 8cf3b0fdf0f..00000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProviderTests.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client; - -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -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.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -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.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.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; - -/** - * Tests for {@link TokenExchangeOAuth2AuthorizedClientProvider}. - * - * @author Steve Riesenberg - */ -public class TokenExchangeOAuth2AuthorizedClientProviderTests { - - private TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider; - - private OAuth2AccessTokenResponseClient accessTokenResponseClient; - - private ClientRegistration clientRegistration; - - private OAuth2Token subjectToken; - - private OAuth2Token actorToken; - - private Authentication principal; - - @BeforeEach - public void setUp() { - this.authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider(); - this.accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class); - this.authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient); - // @formatter:off - this.clientRegistration = ClientRegistration.withRegistrationId("token-exchange") - .clientId("client-id") - .clientSecret("client-secret") - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("read", "write") - .tokenUri("https://example.com/oauth2/token") - .build(); - // @formatter:on - this.subjectToken = TestOAuth2AccessTokens.scopes("read", "write"); - this.actorToken = TestOAuth2AccessTokens.noScopes(); - this.principal = new TestingAuthenticationToken(this.subjectToken, this.subjectToken); - } - - @Test - public void setAccessTokenResponseClientWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setAccessTokenResponseClient(null)) - .withMessage("accessTokenResponseClient cannot be null"); - // @formatter:on - } - - @Test - public void setSubjectTokenResolverWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setSubjectTokenResolver(null)) - .withMessage("subjectTokenResolver cannot be null"); - // @formatter:on - } - - @Test - public void setActorTokenResolverWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setActorTokenResolver(null)) - .withMessage("actorTokenResolver cannot be null"); - // @formatter:on - } - - @Test - public void setClockSkewWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setClockSkew(null)) - .withMessage("clockSkew cannot be null"); - // @formatter:on - } - - @Test - public void setClockSkewWhenNegativeSecondsThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setClockSkew(Duration.ofSeconds(-1))) - .withMessage("clockSkew must be >= 0"); - // @formatter:on - } - - @Test - public void setClockWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setClock(null)) - .withMessage("clock cannot be null"); - // @formatter:on - } - - @Test - public void authorizeWhenContextIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.authorize(null)) - .withMessage("context cannot be null"); - // @formatter:on - } - - @Test - public void authorizeWhenNotTokenExchangeThenUnableToAuthorize() { - ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials().build(); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(clientRegistration) - .principal(this.principal) - .build(); - // @formatter:on - assertThat(this.authorizedClientProvider.authorize(authorizationContext)).isNull(); - verifyNoInteractions(this.accessTokenResponseClient); - } - - @Test - public void authorizeWhenTokenExchangeAndTokenNotExpiredThenNotReauthorized() { - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, - this.principal.getName(), TestOAuth2AccessTokens.scopes("read", "write")); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withAuthorizedClient(authorizedClient) - .principal(this.principal) - .build(); - // @formatter:on - assertThat(this.authorizedClientProvider.authorize(authorizationContext)).isNull(); - verifyNoInteractions(this.accessTokenResponseClient); - } - - @Test - public void authorizeWhenInvalidRequestThenThrowClientAuthorizationException() { - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willThrow(new OAuth2AuthorizationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST))); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(this.clientRegistration) - .principal(this.principal) - .build(); - // @formatter:on - - // @formatter:off - assertThatExceptionOfType(ClientAuthorizationException.class) - .isThrownBy(() -> this.authorizedClientProvider.authorize(authorizationContext)) - .withMessageContaining(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isNull(); - } - - @Test - public void authorizeWhenTokenExchangeAndTokenExpiredThenReauthorized() { - Instant now = Instant.now(); - Instant issuedAt = now.minus(Duration.ofMinutes(60)); - Instant expiresAt = now.minus(Duration.ofMinutes(30)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token-1234", - issuedAt, expiresAt); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, - this.principal.getName(), accessToken); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(accessTokenResponse); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withAuthorizedClient(authorizedClient) - .principal(this.principal) - .build(); - // @formatter:on - OAuth2AuthorizedClient reauthorizedClient = this.authorizedClientProvider.authorize(authorizationContext); - assertThat(reauthorizedClient).isNotNull(); - assertThat(reauthorizedClient).isNotEqualTo(authorizedClient); - assertThat(reauthorizedClient.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(reauthorizedClient.getPrincipalName()).isEqualTo(this.principal.getName()); - assertThat(reauthorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isNull(); - } - - @Test - public void authorizeWhenTokenExchangeAndTokenNotExpiredButClockSkewForcesExpiryThenReauthorized() { - Instant now = Instant.now(); - Instant issuedAt = now.minus(Duration.ofMinutes(60)); - Instant expiresAt = now.plus(Duration.ofMinutes(1)); - OAuth2AccessToken expiresInOneMinAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - "access-token-1234", issuedAt, expiresAt); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, - this.principal.getName(), expiresInOneMinAccessToken); - // Shorten the lifespan of the access token by 90 seconds, which will ultimately - // force it to expire on the client - this.authorizedClientProvider.setClockSkew(Duration.ofSeconds(90)); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(accessTokenResponse); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withAuthorizedClient(authorizedClient) - .principal(this.principal) - .build(); - // @formatter:on - OAuth2AuthorizedClient reauthorizedClient = this.authorizedClientProvider.authorize(authorizationContext); - assertThat(reauthorizedClient).isNotNull(); - assertThat(reauthorizedClient).isNotEqualTo(authorizedClient); - assertThat(reauthorizedClient.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(reauthorizedClient.getPrincipalName()).isEqualTo(this.principal.getName()); - assertThat(reauthorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isNull(); - } - - @Test - public void authorizeWhenTokenExchangeAndNotAuthorizedAndSubjectTokenDoesNotResolveThenUnableToAuthorize() { - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(this.clientRegistration) - .principal(new TestingAuthenticationToken("user", "password")) - .build(); - // @formatter:on - assertThat(this.authorizedClientProvider.authorize(authorizationContext)).isNull(); - verifyNoInteractions(this.accessTokenResponseClient); - } - - @Test - public void authorizeWhenTokenExchangeAndNotAuthorizedAndSubjectTokenResolvesThenAuthorized() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(accessTokenResponse); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(this.clientRegistration) - .principal(this.principal) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientProvider.authorize(authorizationContext); - assertThat(authorizedClient).isNotNull(); - assertThat(authorizedClient.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principal.getName()); - assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isNull(); - } - - @Test - public void authorizeWhenCustomSubjectTokenResolverSetThenCalled() { - Function subjectTokenResolver = mock(Function.class); - given(subjectTokenResolver.apply(any(OAuth2AuthorizationContext.class))).willReturn(this.subjectToken); - this.authorizedClientProvider.setSubjectTokenResolver(subjectTokenResolver); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(accessTokenResponse); - TestingAuthenticationToken principal = new TestingAuthenticationToken("user", "password"); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(this.clientRegistration) - .principal(principal) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientProvider.authorize(authorizationContext); - assertThat(authorizedClient).isNotNull(); - assertThat(authorizedClient.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(authorizedClient.getPrincipalName()).isEqualTo(principal.getName()); - assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - verify(subjectTokenResolver).apply(authorizationContext); - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isNull(); - } - - @Test - public void authorizeWhenCustomActorTokenResolverSetThenCalled() { - Function actorTokenResolver = mock(Function.class); - given(actorTokenResolver.apply(any(OAuth2AuthorizationContext.class))).willReturn(this.actorToken); - this.authorizedClientProvider.setActorTokenResolver(actorTokenResolver); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(accessTokenResponse); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(this.clientRegistration) - .principal(this.principal) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientProvider.authorize(authorizationContext); - assertThat(authorizedClient).isNotNull(); - assertThat(authorizedClient.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principal.getName()); - assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - verify(actorTokenResolver).apply(authorizationContext); - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isEqualTo(this.actorToken); - } - - @Test - public void authorizeWhenClockSetThenCalled() { - Clock clock = mock(Clock.class); - given(clock.instant()).willReturn(Instant.now()); - this.authorizedClientProvider.setClock(clock); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, - this.principal.getName(), TestOAuth2AccessTokens.noScopes()); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withAuthorizedClient(authorizedClient) - .principal(this.principal) - .build(); - // @formatter:on - assertThat(this.authorizedClientProvider.authorize(authorizationContext)).isNull(); - verify(clock).instant(); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/TokenExchangeReactiveOAuth2AuthorizedClientProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/TokenExchangeReactiveOAuth2AuthorizedClientProviderTests.java deleted file mode 100644 index 2b7250911f7..00000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/TokenExchangeReactiveOAuth2AuthorizedClientProviderTests.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client; - -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.util.function.Function; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -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.OAuth2AuthorizationException; -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.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.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; - -/** - * Tests for {@link TokenExchangeReactiveOAuth2AuthorizedClientProvider}. - * - * @author Steve Riesenberg - */ -public class TokenExchangeReactiveOAuth2AuthorizedClientProviderTests { - - private TokenExchangeReactiveOAuth2AuthorizedClientProvider authorizedClientProvider; - - private ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient; - - private ClientRegistration clientRegistration; - - private OAuth2Token subjectToken; - - private OAuth2Token actorToken; - - private Authentication principal; - - @BeforeEach - public void setUp() { - this.authorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider(); - this.accessTokenResponseClient = mock(ReactiveOAuth2AccessTokenResponseClient.class); - this.authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient); - // @formatter:off - this.clientRegistration = ClientRegistration.withRegistrationId("token-exchange") - .clientId("client-id") - .clientSecret("client-secret") - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("read", "write") - .tokenUri("https://example.com/oauth2/token") - .build(); - // @formatter:on - this.subjectToken = TestOAuth2AccessTokens.scopes("read", "write"); - this.actorToken = TestOAuth2AccessTokens.noScopes(); - this.principal = new TestingAuthenticationToken(this.subjectToken, this.subjectToken); - } - - @Test - public void setAccessTokenResponseClientWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setAccessTokenResponseClient(null)) - .withMessage("accessTokenResponseClient cannot be null"); - // @formatter:on - } - - @Test - public void setSubjectTokenResolverWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setSubjectTokenResolver(null)) - .withMessage("subjectTokenResolver cannot be null"); - // @formatter:on - } - - @Test - public void setActorTokenResolverWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setActorTokenResolver(null)) - .withMessage("actorTokenResolver cannot be null"); - // @formatter:on - } - - @Test - public void setClockSkewWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setClockSkew(null)) - .withMessage("clockSkew cannot be null"); - // @formatter:on - } - - @Test - public void setClockSkewWhenNegativeSecondsThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setClockSkew(Duration.ofSeconds(-1))) - .withMessage("clockSkew must be >= 0"); - // @formatter:on - } - - @Test - public void setClockWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.setClock(null)) - .withMessage("clock cannot be null"); - // @formatter:on - } - - @Test - public void authorizeWhenContextIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizedClientProvider.authorize(null).block()) - .withMessage("context cannot be null"); - // @formatter:on - } - - @Test - public void authorizeWhenNotTokenExchangeThenUnableToAuthorize() { - ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials().build(); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(clientRegistration) - .principal(this.principal) - .build(); - // @formatter:on - assertThat(this.authorizedClientProvider.authorize(authorizationContext).block()).isNull(); - verifyNoInteractions(this.accessTokenResponseClient); - } - - @Test - public void authorizeWhenTokenExchangeAndTokenNotExpiredThenNotReauthorized() { - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, - this.principal.getName(), TestOAuth2AccessTokens.scopes("read", "write")); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withAuthorizedClient(authorizedClient) - .principal(this.principal) - .build(); - // @formatter:on - assertThat(this.authorizedClientProvider.authorize(authorizationContext).block()).isNull(); - verifyNoInteractions(this.accessTokenResponseClient); - } - - @Test - public void authorizeWhenInvalidRequestThenThrowClientAuthorizationException() { - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))).willReturn( - Mono.error(new OAuth2AuthorizationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST)))); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(this.clientRegistration) - .principal(this.principal) - .build(); - // @formatter:on - - // @formatter:off - assertThatExceptionOfType(ClientAuthorizationException.class) - .isThrownBy(() -> this.authorizedClientProvider.authorize(authorizationContext).block()) - .satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST)) - .withMessageContaining("[invalid_request]"); - // @formatter:on - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isNull(); - } - - @Test - public void authorizeWhenTokenExchangeAndTokenExpiredThenReauthorized() { - Instant now = Instant.now(); - Instant issuedAt = now.minus(Duration.ofMinutes(60)); - Instant expiresAt = now.minus(Duration.ofMinutes(30)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token-1234", - issuedAt, expiresAt); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, - this.principal.getName(), accessToken); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withAuthorizedClient(authorizedClient) - .principal(this.principal) - .build(); - // @formatter:on - OAuth2AuthorizedClient reauthorizedClient = this.authorizedClientProvider.authorize(authorizationContext) - .block(); - assertThat(reauthorizedClient).isNotNull(); - assertThat(reauthorizedClient).isNotEqualTo(authorizedClient); - assertThat(reauthorizedClient.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(reauthorizedClient.getPrincipalName()).isEqualTo(this.principal.getName()); - assertThat(reauthorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isNull(); - } - - @Test - public void authorizeWhenTokenExchangeAndTokenNotExpiredButClockSkewForcesExpiryThenReauthorized() { - Instant now = Instant.now(); - Instant issuedAt = now.minus(Duration.ofMinutes(60)); - Instant expiresAt = now.plus(Duration.ofMinutes(1)); - OAuth2AccessToken expiresInOneMinAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - "access-token-1234", issuedAt, expiresAt); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, - this.principal.getName(), expiresInOneMinAccessToken); - // Shorten the lifespan of the access token by 90 seconds, which will ultimately - // force it to expire on the client - this.authorizedClientProvider.setClockSkew(Duration.ofSeconds(90)); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withAuthorizedClient(authorizedClient) - .principal(this.principal) - .build(); - // @formatter:on - OAuth2AuthorizedClient reauthorizedClient = this.authorizedClientProvider.authorize(authorizationContext) - .block(); - assertThat(reauthorizedClient).isNotNull(); - assertThat(reauthorizedClient).isNotEqualTo(authorizedClient); - assertThat(reauthorizedClient.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(reauthorizedClient.getPrincipalName()).isEqualTo(this.principal.getName()); - assertThat(reauthorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isNull(); - } - - @Test - public void authorizeWhenTokenExchangeAndNotAuthorizedAndSubjectTokenDoesNotResolveThenUnableToAuthorize() { - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(this.clientRegistration) - .principal(new TestingAuthenticationToken("user", "password")) - .build(); - // @formatter:on - assertThat(this.authorizedClientProvider.authorize(authorizationContext).block()).isNull(); - verifyNoInteractions(this.accessTokenResponseClient); - } - - @Test - public void authorizeWhenTokenExchangeAndNotAuthorizedAndSubjectTokenResolvesThenAuthorized() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(this.clientRegistration) - .principal(this.principal) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientProvider.authorize(authorizationContext).block(); - assertThat(authorizedClient).isNotNull(); - assertThat(authorizedClient.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principal.getName()); - assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isNull(); - } - - @Test - public void authorizeWhenCustomSubjectTokenResolverSetThenCalled() { - Function> subjectTokenResolver = mock(Function.class); - given(subjectTokenResolver.apply(any(OAuth2AuthorizationContext.class))) - .willReturn(Mono.just(this.subjectToken)); - this.authorizedClientProvider.setSubjectTokenResolver(subjectTokenResolver); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - TestingAuthenticationToken principal = new TestingAuthenticationToken("user", "password"); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(this.clientRegistration) - .principal(principal) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientProvider.authorize(authorizationContext).block(); - assertThat(authorizedClient).isNotNull(); - assertThat(authorizedClient.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(authorizedClient.getPrincipalName()).isEqualTo(principal.getName()); - assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - verify(subjectTokenResolver).apply(authorizationContext); - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isNull(); - } - - @Test - public void authorizeWhenCustomActorTokenResolverSetThenCalled() { - Function> actorTokenResolver = mock(Function.class); - given(actorTokenResolver.apply(any(OAuth2AuthorizationContext.class))).willReturn(Mono.just(this.actorToken)); - this.authorizedClientProvider.setActorTokenResolver(actorTokenResolver); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withClientRegistration(this.clientRegistration) - .principal(this.principal) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientProvider.authorize(authorizationContext).block(); - assertThat(authorizedClient).isNotNull(); - assertThat(authorizedClient.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principal.getName()); - assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - verify(actorTokenResolver).apply(authorizationContext); - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(this.accessTokenResponseClient).getTokenResponse(grantRequestCaptor.capture()); - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getSubjectToken()).isEqualTo(this.subjectToken); - assertThat(grantRequest.getActorToken()).isEqualTo(this.actorToken); - } - - @Test - public void authorizeWhenClockSetThenCalled() { - Clock clock = mock(Clock.class); - given(clock.instant()).willReturn(Instant.now()); - this.authorizedClientProvider.setClock(clock); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, - this.principal.getName(), TestOAuth2AccessTokens.noScopes()); - // @formatter:off - OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext - .withAuthorizedClient(authorizedClient) - .principal(this.principal) - .build(); - // @formatter:on - assertThat(this.authorizedClientProvider.authorize(authorizationContext).block()).isNull(); - verify(clock).instant(); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClientTests.java deleted file mode 100644 index 9624c6465cf..00000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClientTests.java +++ /dev/null @@ -1,491 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client.endpoint; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Instant; - -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.core.convert.converter.Converter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jwt.TestJwts; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestOperations; - -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.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 DefaultJwtBearerTokenResponseClient}. - * - * @author Steve Riesenberg - */ -public class DefaultTokenExchangeTokenResponseClientTests { - - 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 DefaultTokenExchangeTokenResponseClient tokenResponseClient; - - private ClientRegistration.Builder clientRegistration; - - private OAuth2Token subjectToken; - - private OAuth2Token actorToken; - - private MockWebServer server; - - @BeforeEach - public void setUp() throws IOException { - this.tokenResponseClient = new DefaultTokenExchangeTokenResponseClient(); - this.server = new MockWebServer(); - this.server.start(); - String tokenUri = this.server.url("/oauth2/token").toString(); - // @formatter:off - this.clientRegistration = TestClientRegistrations.clientCredentials() - .clientId("client-1") - .clientSecret("secret") - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .tokenUri(tokenUri) - .scope("read", "write"); - // @formatter:on - this.subjectToken = TestOAuth2AccessTokens.scopes("read", "write"); - this.actorToken = null; - } - - @AfterEach - public void cleanUp() throws IOException { - this.server.shutdown(); - } - - @Test - public void setRequestEntityConverterWhenConverterIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.setRequestEntityConverter(null)) - .withMessage("requestEntityConverter cannot be null"); - // @formatter:on - } - - @Test - public void setRestOperationsWhenRestOperationsIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.setRestOperations(null)) - .withMessage("restOperations cannot be null"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenGrantRequestIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(null)) - .withMessage("grantRequest cannot be null"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"read write\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)) - .isEqualTo(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"); - assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) - .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); - String formParameters = recordedRequest.getBody().readUtf8(); - // @formatter:off - assertThat(formParameters).contains( - param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), - param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), - param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) - ); - // @formatter:on - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); - assertThat(accessTokenResponse.getRefreshToken()).isNull(); - } - - @Test - public void getTokenResponseWhenSubjectTokenIsJwtThenSubjectTokenTypeIsJwt() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"read write\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); - this.subjectToken = TestJwts.jwt().build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)) - .isEqualTo(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"); - assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) - .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); - String formParameters = recordedRequest.getBody().readUtf8(); - // @formatter:off - assertThat(formParameters).contains( - param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), - param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), - param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) - ); - // @formatter:on - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); - assertThat(accessTokenResponse.getRefreshToken()).isNull(); - } - - @Test - public void getTokenResponseWhenActorTokenIsNotNullThenActorParametersAreSent() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"read write\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); - this.actorToken = TestOAuth2AccessTokens.noScopes(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)) - .isEqualTo(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"); - assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) - .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); - String formParameters = recordedRequest.getBody().readUtf8(); - // @formatter:off - assertThat(formParameters).contains( - param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), - param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), - param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.ACTOR_TOKEN, this.actorToken.getTokenValue()), - param(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) - ); - // @formatter:on - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); - assertThat(accessTokenResponse.getRefreshToken()).isNull(); - } - - @Test - public void getTokenResponseWhenActorTokenIsJwtThenActorTokenTypeIsJwt() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"read write\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); - this.actorToken = TestJwts.jwt().build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)) - .isEqualTo(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"); - assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) - .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); - String formParameters = recordedRequest.getBody().readUtf8(); - // @formatter:off - assertThat(formParameters).contains( - param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), - param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), - param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.ACTOR_TOKEN, this.actorToken.getTokenValue()), - param(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) - ); - // @formatter:on - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); - assertThat(accessTokenResponse.getRefreshToken()).isNull(); - } - - @Test - public void getTokenResponseWhenAuthenticationClientSecretBasicThenAuthorizationHeaderIsSent() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - this.tokenResponseClient.getTokenResponse(grantRequest); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); - } - - @Test - public void getTokenResponseWhenAuthenticationClientSecretPostThenFormParametersAreSent() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - ClientRegistration clientRegistration = this.clientRegistration - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) - .build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - this.tokenResponseClient.getTokenResponse(grantRequest); - RecordedRequest recordedRequest = this.server.takeRequest(); - String formParameters = recordedRequest.getBody().readUtf8(); - assertThat(formParameters).contains("client_id=client-1", "client_secret=secret"); - } - - @Test - public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"not-bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest)) - .satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo("invalid_token_response")) - .withMessageContaining("[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response") - .havingRootCause().withMessage("tokenType cannot be null"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasResponseScope() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"read\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read"); - } - - @Test - public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); - assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); - } - - @Test - public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() { - String accessTokenErrorResponse = "{\"error\": \"invalid_grant\"}"; - this.server.enqueue(jsonResponse(accessTokenErrorResponse).setResponseCode(400)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest)) - .satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_GRANT)) - .withMessageContaining("[invalid_grant]"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationException() { - this.server.enqueue(new MockResponse().setResponseCode(500)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest)) - .satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo("invalid_token_response")) - .withMessageContaining("[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenCustomClientAuthenticationMethodThenIllegalArgument() { - ClientRegistration clientRegistration = this.clientRegistration - .clientAuthenticationMethod(new ClientAuthenticationMethod("basic")) - .build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - // @formatter:off - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest)) - .withMessageContaining("This class supports `client_secret_basic`, `client_secret_post`, and `none` by default."); - // @formatter:on - } - - @Test - public void getTokenResponseWhenUnsupportedClientAuthenticationMethodThenIllegalArgument() { - ClientRegistration clientRegistration = this.clientRegistration - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - // @formatter:off - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest)) - .withMessageContaining("This class supports `client_secret_basic`, `client_secret_post`, and `none` by default."); - // @formatter:on - } - - @Test - public void getTokenResponseWhenCustomRequestEntityConverterSetThenCalled() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Converter> requestEntityConverter = spy( - TokenExchangeGrantRequestEntityConverter.class); - this.tokenResponseClient.setRequestEntityConverter(requestEntityConverter); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - this.tokenResponseClient.getTokenResponse(grantRequest); - verify(requestEntityConverter).convert(grantRequest); - } - - @Test - public void getTokenResponseWhenCustomRestOperationsSetThenCalled() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - RestOperations restOperations = mock(RestOperations.class); - given(restOperations.exchange(any(RequestEntity.class), eq(OAuth2AccessTokenResponse.class))) - .willReturn(new ResponseEntity<>(HttpStatus.OK)); - this.tokenResponseClient.setRestOperations(restOperations); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - this.tokenResponseClient.getTokenResponse(grantRequest); - verify(restOperations).exchange(any(RequestEntity.class), eq(OAuth2AccessTokenResponse.class)); - } - - private MockResponse jsonResponse(String json) { - return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json); - } - - private static String param(String parameterName, String parameterValue) { - return "%s=%s".formatted(parameterName, URLEncoder.encode(parameterValue, StandardCharsets.UTF_8)); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverterTests.java deleted file mode 100644 index 8a77a66dfb6..00000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverterTests.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client.endpoint; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InOrder; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jwt.TestJwts; -import org.springframework.util.MultiValueMap; -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.Mockito.inOrder; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link TokenExchangeGrantRequestEntityConverter}. - * - * @author Steve Riesenberg - */ -public class TokenExchangeGrantRequestEntityConverterTests { - - 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 TokenExchangeGrantRequestEntityConverter converter; - - private OAuth2Token subjectToken; - - private OAuth2Token actorToken; - - @BeforeEach - public void setUp() { - this.converter = new TokenExchangeGrantRequestEntityConverter(); - this.subjectToken = TestOAuth2AccessTokens.scopes("read", "write"); - this.actorToken = null; - } - - @Test - public void setHeadersConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.converter.setHeadersConverter(null)) - .withMessage("headersConverter cannot be null"); - // @formatter:on - } - - @Test - public void addHeadersConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.converter.addHeadersConverter(null)) - .withMessage("headersConverter cannot be null"); - // @formatter:on - } - - @Test - public void setParametersConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.converter.setParametersConverter(null)) - .withMessage("parametersConverter cannot be null"); - // @formatter:on - } - - @Test - public void addParametersConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.converter.addParametersConverter(null)) - .withMessage("parametersConverter cannot be null"); - // @formatter:on - } - - @Test - public void convertWhenHeadersConverterSetThenCalled() { - Converter headersConverter1 = mock(Converter.class); - this.converter.setHeadersConverter(headersConverter1); - Converter headersConverter2 = mock(Converter.class); - this.converter.addHeadersConverter(headersConverter2); - // @formatter:off - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("read", "write") - .build(); - // @formatter:on - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - this.converter.convert(grantRequest); - InOrder inOrder = inOrder(headersConverter1, headersConverter2); - inOrder.verify(headersConverter1).convert(grantRequest); - inOrder.verify(headersConverter2).convert(grantRequest); - } - - @Test - public void convertWhenParametersConverterSetThenCalled() { - Converter> parametersConverter1 = mock( - Converter.class); - this.converter.setParametersConverter(parametersConverter1); - Converter> parametersConverter2 = mock( - Converter.class); - this.converter.addParametersConverter(parametersConverter2); - // @formatter:off - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("read", "write") - .build(); - // @formatter:on - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - this.converter.convert(grantRequest); - InOrder inOrder = inOrder(parametersConverter1, parametersConverter2); - inOrder.verify(parametersConverter1).convert(any(TokenExchangeGrantRequest.class)); - inOrder.verify(parametersConverter2).convert(any(TokenExchangeGrantRequest.class)); - } - - @SuppressWarnings("unchecked") - @Test - public void convertWhenGrantRequestValidThenConverts() { - // @formatter:off - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("read", "write") - .build(); - // @formatter:on - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - RequestEntity requestEntity = this.converter.convert(grantRequest); - assertThat(requestEntity).isNotNull(); - assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST); - assertThat(requestEntity.getUrl().toASCIIString()) - .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); - HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()) - .contains(MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8")); - assertThat(headers.getContentType()) - .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); - assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); - MultiValueMap formParameters = (MultiValueMap) requestEntity.getBody(); - assertThat(formParameters).isNotNull(); - assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) - .isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)) - .isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN)) - .isEqualTo(this.subjectToken.getTokenValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)) - .isEqualTo(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); - } - - @SuppressWarnings("unchecked") - @Test - public void convertWhenClientAuthenticationMethodIsClientSecretPostThenClientIdAndSecretParametersPresent() { - // @formatter:off - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) - .scope("read", "write") - .build(); - // @formatter:on - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - RequestEntity requestEntity = this.converter.convert(grantRequest); - assertThat(requestEntity).isNotNull(); - assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST); - assertThat(requestEntity.getUrl().toASCIIString()) - .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); - HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()) - .contains(MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8")); - assertThat(headers.getContentType()) - .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); - assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isNull(); - MultiValueMap formParameters = (MultiValueMap) requestEntity.getBody(); - assertThat(formParameters).isNotNull(); - assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) - .isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)) - .isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN)) - .isEqualTo(this.subjectToken.getTokenValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)) - .isEqualTo(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); - assertThat(formParameters.getFirst(OAuth2ParameterNames.CLIENT_ID)).isEqualTo(clientRegistration.getClientId()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.CLIENT_SECRET)) - .isEqualTo(clientRegistration.getClientSecret()); - } - - @SuppressWarnings("unchecked") - @Test - public void convertWhenActorTokenIsNotNullThenActorTokenParametersPresent() { - // @formatter:off - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("read", "write") - .build(); - // @formatter:on - this.actorToken = TestOAuth2AccessTokens.noScopes(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - RequestEntity requestEntity = this.converter.convert(grantRequest); - assertThat(requestEntity).isNotNull(); - MultiValueMap formParameters = (MultiValueMap) requestEntity.getBody(); - assertThat(formParameters).isNotNull(); - assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) - .isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)) - .isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN)) - .isEqualTo(this.subjectToken.getTokenValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN)) - .isEqualTo(this.actorToken.getTokenValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN_TYPE)).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)) - .isEqualTo(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); - } - - @SuppressWarnings("unchecked") - @Test - public void convertWhenSubjectTokenIsJwtThenSubjectTokenTypeIsJwt() { - // @formatter:off - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("read", "write") - .build(); - // @formatter:on - this.subjectToken = TestJwts.jwt().build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - RequestEntity requestEntity = this.converter.convert(grantRequest); - assertThat(requestEntity).isNotNull(); - MultiValueMap formParameters = (MultiValueMap) requestEntity.getBody(); - assertThat(formParameters).isNotNull(); - assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) - .isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)) - .isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN)) - .isEqualTo(this.subjectToken.getTokenValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).isEqualTo(JWT_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)) - .isEqualTo(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); - } - - @SuppressWarnings("unchecked") - @Test - public void convertWhenActorTokenIsJwtThenActorTokenTypeIsJwt() { - // @formatter:off - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("read", "write") - .build(); - // @formatter:on - this.actorToken = TestJwts.jwt().build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - RequestEntity requestEntity = this.converter.convert(grantRequest); - assertThat(requestEntity).isNotNull(); - MultiValueMap formParameters = (MultiValueMap) requestEntity.getBody(); - assertThat(formParameters).isNotNull(); - assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) - .isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)) - .isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN)) - .isEqualTo(this.subjectToken.getTokenValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN)) - .isEqualTo(this.actorToken.getTokenValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN_TYPE)).isEqualTo(JWT_TOKEN_TYPE_VALUE); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)) - .isEqualTo(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestTests.java deleted file mode 100644 index f8b740eea97..00000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestTests.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client.endpoint; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link TokenExchangeGrantRequest}. - * - * @author Steve Riesenberg - */ -public class TokenExchangeGrantRequestTests { - - private final ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - - private final OAuth2Token subjectToken = TestOAuth2AccessTokens.scopes("read", "write"); - - private final OAuth2Token actorToken = TestOAuth2AccessTokens.noScopes(); - - @Test - public void constructorWhenClientRegistrationIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new TokenExchangeGrantRequest(null, this.subjectToken, this.actorToken)) - .withMessage("clientRegistration cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenSubjectTokenIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new TokenExchangeGrantRequest(this.clientRegistration, null, this.actorToken)) - .withMessage("subjectToken cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenActorTokenIsNullThenCreated() { - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration, - this.subjectToken, null); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(grantRequest.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(grantRequest.getSubjectToken()).isSameAs(this.subjectToken); - } - - @Test - public void constructorWhenClientRegistrationInvalidGrantTypeThenThrowIllegalArgumentException() { - ClientRegistration registration = TestClientRegistrations.clientCredentials().build(); - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new TokenExchangeGrantRequest(registration, this.subjectToken, this.actorToken)) - .withMessage("clientRegistration.authorizationGrantType must be AuthorizationGrantType.TOKEN_EXCHANGE"); - // @formatter:on - } - - @Test - public void constructorWhenValidParametersProvidedThenCreated() { - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration, - this.subjectToken, this.actorToken); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(grantRequest.getClientRegistration()).isSameAs(this.clientRegistration); - assertThat(grantRequest.getSubjectToken()).isSameAs(this.subjectToken); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveTokenExchangeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveTokenExchangeTokenResponseClientTests.java deleted file mode 100644 index c9bbfcf5178..00000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveTokenExchangeTokenResponseClientTests.java +++ /dev/null @@ -1,654 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client.endpoint; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.Collections; - -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.ReactiveHttpInputMessage; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -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.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; -import org.springframework.security.oauth2.jwt.TestJwts; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.web.reactive.function.BodyExtractor; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link WebClientReactiveTokenExchangeTokenResponseClient}. - * - * @author Steve Riesenberg - */ -public class WebClientReactiveTokenExchangeTokenResponseClientTests { - - 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 WebClientReactiveTokenExchangeTokenResponseClient tokenResponseClient; - - private ClientRegistration.Builder clientRegistration; - - private OAuth2Token subjectToken; - - private OAuth2Token actorToken; - - private MockWebServer server; - - @BeforeEach - public void setUp() throws IOException { - this.tokenResponseClient = new WebClientReactiveTokenExchangeTokenResponseClient(); - this.server = new MockWebServer(); - this.server.start(); - String tokenUri = this.server.url("/oauth2/token").toString(); - // @formatter:off - this.clientRegistration = TestClientRegistrations.clientCredentials() - .clientId("client-1") - .clientSecret("secret") - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .tokenUri(tokenUri) - .scope("read", "write"); - // @formatter:on - this.subjectToken = TestOAuth2AccessTokens.scopes("read", "write"); - this.actorToken = null; - } - - @AfterEach - public void cleanUp() throws IOException { - this.server.shutdown(); - } - - @Test - public void setWebClientWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.setWebClient(null)) - .withMessage("webClient cannot be null"); - // @formatter:on - } - - @Test - public void setHeadersConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.setHeadersConverter(null)) - .withMessage("headersConverter cannot be null"); - // @formatter:on - } - - @Test - public void addHeadersConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.addHeadersConverter(null)) - .withMessage("headersConverter cannot be null"); - // @formatter:on - } - - @Test - public void setParametersConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.setParametersConverter(null)) - .withMessage("parametersConverter cannot be null"); - // @formatter:on - } - - @Test - public void addParametersConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.addParametersConverter(null)) - .withMessage("parametersConverter cannot be null"); - // @formatter:on - } - - @Test - public void setBodyExtractorWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.setBodyExtractor(null)) - .withMessage("bodyExtractor cannot be null"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenGrantRequestIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(null)) - .withMessage("grantRequest cannot be null"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"read write\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest).block(); - assertThat(accessTokenResponse).isNotNull(); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); - assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) - .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); - String formParameters = recordedRequest.getBody().readUtf8(); - // @formatter:off - assertThat(formParameters).contains( - param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), - param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), - param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) - ); - // @formatter:on - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); - assertThat(accessTokenResponse.getRefreshToken()).isNull(); - } - - @Test - public void getTokenResponseWhenSubjectTokenIsJwtThenSubjectTokenTypeIsJwt() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"read write\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); - this.subjectToken = TestJwts.jwt().build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest).block(); - assertThat(accessTokenResponse).isNotNull(); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); - assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) - .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); - String formParameters = recordedRequest.getBody().readUtf8(); - // @formatter:off - assertThat(formParameters).contains( - param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), - param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), - param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) - ); - // @formatter:on - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); - assertThat(accessTokenResponse.getRefreshToken()).isNull(); - } - - @Test - public void getTokenResponseWhenActorTokenIsNotNullThenActorParametersAreSent() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"read write\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); - this.actorToken = TestOAuth2AccessTokens.noScopes(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest).block(); - assertThat(accessTokenResponse).isNotNull(); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); - assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) - .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); - String formParameters = recordedRequest.getBody().readUtf8(); - // @formatter:off - assertThat(formParameters).contains( - param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), - param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), - param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.ACTOR_TOKEN, this.actorToken.getTokenValue()), - param(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) - ); - // @formatter:on - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); - assertThat(accessTokenResponse.getRefreshToken()).isNull(); - } - - @Test - public void getTokenResponseWhenActorTokenIsJwtThenActorTokenTypeIsJwt() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"read write\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); - this.actorToken = TestJwts.jwt().build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest).block(); - assertThat(accessTokenResponse).isNotNull(); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); - assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) - .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); - String formParameters = recordedRequest.getBody().readUtf8(); - // @formatter:off - assertThat(formParameters).contains( - param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), - param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), - param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.ACTOR_TOKEN, this.actorToken.getTokenValue()), - param(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) - ); - // @formatter:on - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); - assertThat(accessTokenResponse.getRefreshToken()).isNull(); - } - - @Test - public void getTokenResponseWhenAuthenticationClientSecretBasicThenAuthorizationHeaderIsSent() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - this.tokenResponseClient.getTokenResponse(grantRequest).block(); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); - } - - @Test - public void getTokenResponseWhenAuthenticationClientSecretPostThenFormParametersAreSent() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - ClientRegistration clientRegistration = this.clientRegistration - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) - .build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - this.tokenResponseClient.getTokenResponse(grantRequest).block(); - RecordedRequest recordedRequest = this.server.takeRequest(); - String formParameters = recordedRequest.getBody().readUtf8(); - assertThat(formParameters).contains("client_id=client-1", "client_secret=secret"); - } - - @Test - public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"not-bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest).block()) - .satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo("invalid_token_response")) - .withMessageContaining("[invalid_token_response] An error occurred parsing the Access Token response") - .havingRootCause().withMessage("Unsupported token_type: not-bearer"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasResponseScope() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"read\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest).block(); - assertThat(accessTokenResponse).isNotNull(); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read"); - } - - @Test - public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest).block(); - assertThat(accessTokenResponse).isNotNull(); - assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); - } - - @Test - public void getTokenResponseWhenInvalidResponseThenThrowOAuth2AuthorizationException() { - this.server.enqueue(new MockResponse().setResponseCode(301)); - TokenExchangeGrantRequest request = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(request).block()) - .satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo("invalid_token_response")) - .withMessage("[invalid_token_response] Empty OAuth 2.0 Access Token Response"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationException() { - String accessTokenErrorResponse = "{\"error\": \"server_error\", \"error_description\": \"A server error occurred\"}"; - this.server.enqueue(jsonResponse(accessTokenErrorResponse).setResponseCode(500)); - TokenExchangeGrantRequest request = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(request).block()) - .satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo(OAuth2ErrorCodes.SERVER_ERROR)) - .withMessage("[server_error] A server error occurred"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() { - String accessTokenErrorResponse = "{\"error\": \"invalid_grant\", \"error_description\": \"Invalid grant\"}"; - this.server.enqueue(jsonResponse(accessTokenErrorResponse).setResponseCode(400)); - TokenExchangeGrantRequest request = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(request).block()) - .satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_GRANT)) - .withMessage("[invalid_grant] Invalid grant"); - // @formatter:on - } - - @Test - public void getTokenResponseWhenCustomClientAuthenticationMethodThenIllegalArgument() { - ClientRegistration clientRegistration = this.clientRegistration - .clientAuthenticationMethod(new ClientAuthenticationMethod("basic")) - .build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - // @formatter:off - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest).block()) - .withMessageContaining("This class supports `client_secret_basic`, `client_secret_post`, and `none` by default."); - // @formatter:on - } - - @Test - public void getTokenResponseWhenUnsupportedClientAuthenticationMethodThenIllegalArgument() { - ClientRegistration clientRegistration = this.clientRegistration - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - // @formatter:off - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest).block()) - .withMessageContaining("This class supports `client_secret_basic`, `client_secret_post`, and `none` by default."); - // @formatter:on - } - - @Test - public void getTokenResponseWhenHeadersConverterAddedThenCalled() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - Converter headersConverter = mock(Converter.class); - HttpHeaders headers = new HttpHeaders(); - headers.put("custom-header-name", Collections.singletonList("custom-header-value")); - given(headersConverter.convert(grantRequest)).willReturn(headers); - this.tokenResponseClient.addHeadersConverter(headersConverter); - this.tokenResponseClient.getTokenResponse(grantRequest).block(); - verify(headersConverter).convert(grantRequest); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); - assertThat(recordedRequest.getHeader("custom-header-name")).isEqualTo("custom-header-value"); - } - - @Test - public void getTokenResponseWhenHeadersConverterSetThenCalled() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - Converter headersConverter = mock(Converter.class); - HttpHeaders headers = new HttpHeaders(); - headers.put("custom-header-name", Collections.singletonList("custom-header-value")); - given(headersConverter.convert(grantRequest)).willReturn(headers); - this.tokenResponseClient.setHeadersConverter(headersConverter); - this.tokenResponseClient.getTokenResponse(grantRequest).block(); - verify(headersConverter).convert(grantRequest); - RecordedRequest recordedRequest = this.server.takeRequest(); - assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); - assertThat(recordedRequest.getHeader("custom-header-name")).isEqualTo("custom-header-value"); - } - - @Test - public void getTokenResponseWhenParametersConverterSetThenCalled() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - Converter> parametersConverter = mock(Converter.class); - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.add("custom-parameter-name", "custom-parameter-value"); - given(parametersConverter.convert(grantRequest)).willReturn(parameters); - this.tokenResponseClient.setParametersConverter(parametersConverter); - this.tokenResponseClient.getTokenResponse(grantRequest).block(); - verify(parametersConverter).convert(grantRequest); - RecordedRequest recordedRequest = this.server.takeRequest(); - String formParameters = recordedRequest.getBody().readUtf8(); - assertThat(formParameters).contains("custom-parameter-name=custom-parameter-value"); - } - - @Test - public void getTokenResponseWhenParametersConverterAddedThenCalled() throws Exception { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), - this.subjectToken, this.actorToken); - Converter> parametersConverter = mock(Converter.class); - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.add("custom-parameter-name", "custom-parameter-value"); - given(parametersConverter.convert(grantRequest)).willReturn(parameters); - this.tokenResponseClient.addParametersConverter(parametersConverter); - this.tokenResponseClient.getTokenResponse(grantRequest).block(); - verify(parametersConverter).convert(grantRequest); - RecordedRequest recordedRequest = this.server.takeRequest(); - String formParameters = recordedRequest.getBody().readUtf8(); - // @formatter:off - assertThat(formParameters).contains( - param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), - param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), - param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), - param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")), - param("custom-parameter-name", "custom-parameter-value") - ); - // @formatter:on - } - - @Test - public void getTokenResponseWhenBodyExtractorSetThenCalled() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - BodyExtractor, ReactiveHttpInputMessage> bodyExtractor = mock( - BodyExtractor.class); - OAuth2AccessTokenResponse response = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(bodyExtractor.extract(any(ReactiveHttpInputMessage.class), any(BodyExtractor.Context.class))) - .willReturn(Mono.just(response)); - ClientRegistration clientRegistration = this.clientRegistration.build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - this.tokenResponseClient.setBodyExtractor(bodyExtractor); - this.tokenResponseClient.getTokenResponse(grantRequest).block(); - verify(bodyExtractor).extract(any(ReactiveHttpInputMessage.class), any(BodyExtractor.Context.class)); - } - - @Test - public void getTokenResponseWhenWebClientSetThenCalled() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - WebClient customClient = mock(WebClient.class); - given(customClient.post()).willReturn(WebClient.builder().build().post()); - this.tokenResponseClient.setWebClient(customClient); - ClientRegistration clientRegistration = this.clientRegistration.build(); - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, - this.actorToken); - this.tokenResponseClient.getTokenResponse(grantRequest).block(); - verify(customClient).post(); - } - - private MockResponse jsonResponse(String json) { - return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json); - } - - private static String param(String parameterName, String parameterValue) { - return "%s=%s".formatted(parameterName, URLEncoder.encode(parameterValue, StandardCharsets.UTF_8)); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactoryTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactoryTests.java index 8c5b70ea494..3a3f668d7a3 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactoryTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactoryTests.java @@ -46,7 +46,6 @@ /** * @author Joe Grandja * @author Rafael Dominguez - * @author Ubaid ur Rehman * @since 5.2 */ public class ReactiveOidcIdTokenDecoderFactoryTests { diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserServiceTests.java index ade191c6da1..2b8e6180dfc 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -16,49 +16,37 @@ package org.springframework.security.oauth2.client.oidc.userinfo; -import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.function.BiFunction; import java.util.function.Function; -import java.util.function.Predicate; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; 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.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import reactor.core.publisher.Mono; import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService; -import org.springframework.security.oauth2.core.AuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; import org.springframework.security.oauth2.core.oidc.StandardClaimNames; import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens; -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -68,8 +56,6 @@ 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.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.same; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -114,15 +100,6 @@ public void setClaimTypeConverterFactoryWhenNullThenThrowIllegalArgumentExceptio assertThatIllegalArgumentException().isThrownBy(() -> this.userService.setClaimTypeConverterFactory(null)); } - @Test - public void setRetrieveUserInfoWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.userService.setRetrieveUserInfo(null)) - .withMessage("retrieveUserInfo cannot be null"); - // @formatter:on - } - @Test public void loadUserWhenUserInfoUriNullThenUserInfoNotRetrieved() { this.registration.userInfoUri(null); @@ -199,95 +176,6 @@ public void loadUserWhenCustomClaimTypeConverterFactorySetThenApplied() { verify(customClaimTypeConverterFactory).apply(same(userRequest.getClientRegistration())); } - @Test - public void loadUserWhenTokenScopesIsEmptyThenUserInfoNotRetrieved() { - // @formatter:off - OAuth2AccessToken accessToken = new OAuth2AccessToken( - this.accessToken.getTokenType(), - this.accessToken.getTokenValue(), - this.accessToken.getIssuedAt(), - this.accessToken.getExpiresAt(), - Collections.emptySet()); - // @formatter:on - OidcUserRequest userRequest = new OidcUserRequest(this.registration.build(), accessToken, this.idToken); - OidcUser oidcUser = this.userService.loadUser(userRequest).block(); - assertThat(oidcUser).isNotNull(); - assertThat(oidcUser.getUserInfo()).isNull(); - } - - @Test - public void loadUserWhenCustomRetrieveUserInfoSetThenUsed() { - Map attributes = new HashMap<>(); - attributes.put(StandardClaimNames.SUB, "subject"); - attributes.put("user", "steve"); - OAuth2User oauth2User = new DefaultOAuth2User(AuthorityUtils.createAuthorityList("ROLE_USER"), attributes, - "user"); - given(this.oauth2UserService.loadUser(any())).willReturn(Mono.just(oauth2User)); - Predicate customRetrieveUserInfo = mock(Predicate.class); - this.userService.setRetrieveUserInfo(customRetrieveUserInfo); - given(customRetrieveUserInfo.test(any(OidcUserRequest.class))).willReturn(true); - // @formatter:off - OAuth2AccessToken accessToken = new OAuth2AccessToken( - this.accessToken.getTokenType(), - this.accessToken.getTokenValue(), - this.accessToken.getIssuedAt(), - this.accessToken.getExpiresAt(), - Collections.emptySet()); - // @formatter:on - OidcUserRequest userRequest = new OidcUserRequest(this.registration.build(), accessToken, this.idToken); - OidcUser oidcUser = this.userService.loadUser(userRequest).block(); - assertThat(oidcUser).isNotNull(); - assertThat(oidcUser.getUserInfo()).isNotNull(); - verify(customRetrieveUserInfo).test(userRequest); - } - - @Test - public void loadUserWhenCustomOidcUserMapperSetThenUsed() { - Map attributes = new HashMap<>(); - attributes.put(StandardClaimNames.SUB, "subject"); - attributes.put("user", "steve"); - OAuth2User oauth2User = new DefaultOAuth2User(AuthorityUtils.createAuthorityList("ROLE_USER"), attributes, - "user"); - given(this.oauth2UserService.loadUser(any(OidcUserRequest.class))).willReturn(Mono.just(oauth2User)); - BiFunction> customOidcUserMapper = mock(BiFunction.class); - OidcUser actualUser = new DefaultOidcUser(AuthorityUtils.createAuthorityList("a", "b"), this.idToken, - IdTokenClaimNames.SUB); - given(customOidcUserMapper.apply(any(OidcUserRequest.class), any(OidcUserInfo.class))) - .willReturn(Mono.just(actualUser)); - this.userService.setOidcUserMapper(customOidcUserMapper); - OidcUserRequest userRequest = userRequest(); - OidcUser oidcUser = this.userService.loadUser(userRequest).block(); - assertThat(oidcUser).isNotNull(); - assertThat(oidcUser).isEqualTo(actualUser); - ArgumentCaptor userInfoCaptor = ArgumentCaptor.forClass(OidcUserInfo.class); - verify(customOidcUserMapper).apply(eq(userRequest), userInfoCaptor.capture()); - OidcUserInfo userInfo = userInfoCaptor.getValue(); - assertThat(userInfo.getSubject()).isEqualTo("subject"); - assertThat(userInfo.getClaimAsString("user")).isEqualTo("steve"); - } - - @Test - public void loadUserWhenCustomOidcUserMapperSetAndUserInfoNotRetrievedThenUsed() { - // @formatter:off - this.accessToken = new OAuth2AccessToken( - this.accessToken.getTokenType(), - this.accessToken.getTokenValue(), - this.accessToken.getIssuedAt(), - this.accessToken.getExpiresAt(), - Collections.emptySet()); - // @formatter:on - BiFunction> customOidcUserMapper = mock(BiFunction.class); - OidcUser actualUser = new DefaultOidcUser(AuthorityUtils.createAuthorityList("a", "b"), this.idToken, - IdTokenClaimNames.SUB); - given(customOidcUserMapper.apply(any(OidcUserRequest.class), isNull())).willReturn(Mono.just(actualUser)); - this.userService.setOidcUserMapper(customOidcUserMapper); - OidcUserRequest userRequest = userRequest(); - OidcUser oidcUser = this.userService.loadUser(userRequest).block(); - assertThat(oidcUser).isNotNull(); - assertThat(oidcUser).isEqualTo(actualUser); - verify(customOidcUserMapper).apply(eq(userRequest), isNull(OidcUserInfo.class)); - } - @Test public void loadUserWhenTokenContainsScopesThenIndividualScopeAuthorities() { OidcReactiveOAuth2UserService userService = new OidcReactiveOAuth2UserService(); @@ -315,62 +203,8 @@ public void loadUserWhenTokenDoesNotContainScopesThenNoScopeAuthorities() { assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); } - @Test - public void loadUserWhenNestedUserInfoSuccessThenReturnUser() throws IOException { - // @formatter:off - String userInfoResponse = "{\n" - + " \"user\": {\"user-name\": \"user1\"},\n" - + " \"sub\" : \"" + this.idToken.getSubject() + "\",\n" - + " \"first-name\": \"first\",\n" - + " \"last-name\": \"last\",\n" - + " \"middle-name\": \"middle\",\n" - + " \"address\": \"address\",\n" - + " \"email\": \"user1@example.com\"\n" - + "}\n"; - // @formatter:on - try (MockWebServer server = new MockWebServer()) { - server.start(); - enqueueApplicationJsonBody(server, userInfoResponse); - String userInfoUri = server.url("/user").toString(); - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() - .userInfoUri(userInfoUri) - .userInfoAuthenticationMethod(AuthenticationMethod.HEADER) - .userNameAttributeName("user-name") - .build(); - OidcReactiveOAuth2UserService userService = new OidcReactiveOAuth2UserService(); - DefaultReactiveOAuth2UserService oAuth2UserService = new DefaultReactiveOAuth2UserService(); - oAuth2UserService.setAttributesConverter((request) -> (attributes) -> { - Map user = (Map) attributes.get("user"); - attributes.put("user-name", user.get("user-name")); - return attributes; - }); - userService.setOauth2UserService(oAuth2UserService); - OAuth2User user = userService - .loadUser(new OidcUserRequest(clientRegistration, this.accessToken, this.idToken)) - .block(); - assertThat(user.getName()).isEqualTo("user1"); - assertThat(user.getAttributes()).hasSize(13); - assertThat(((Map) user.getAttribute("user")).get("user-name")).isEqualTo("user1"); - assertThat((String) user.getAttribute("first-name")).isEqualTo("first"); - assertThat((String) user.getAttribute("last-name")).isEqualTo("last"); - assertThat((String) user.getAttribute("middle-name")).isEqualTo("middle"); - assertThat((String) user.getAttribute("address")).isEqualTo("address"); - assertThat((String) user.getAttribute("email")).isEqualTo("user1@example.com"); - assertThat(user.getAuthorities()).hasSize(2); - assertThat(user.getAuthorities().iterator().next()).isInstanceOf(OAuth2UserAuthority.class); - OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); - assertThat(userAuthority.getAuthority()).isEqualTo("OIDC_USER"); - assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); - } - } - private OidcUserRequest userRequest() { return new OidcUserRequest(this.registration.build(), this.accessToken, this.idToken); } - private void enqueueApplicationJsonBody(MockWebServer server, String json) { - server.enqueue( - new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json)); - } - } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserServiceTests.java index f9982c82262..310667e2fff 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -22,9 +22,7 @@ import java.util.Iterator; import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; import java.util.function.Function; -import java.util.function.Predicate; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -32,14 +30,12 @@ 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.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; @@ -52,20 +48,14 @@ 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.OidcUserInfo; import org.springframework.security.oauth2.core.oidc.StandardClaimNames; import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens; -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; 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.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -137,24 +127,6 @@ public void setAccessibleScopesWhenEmptyThenSet() { this.userService.setAccessibleScopes(Collections.emptySet()); } - @Test - public void setRetrieveUserInfoWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.userService.setRetrieveUserInfo(null)) - .withMessage("retrieveUserInfo cannot be null"); - // @formatter:on - } - - @Test - public void setOidcUserMapperWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.userService.setOidcUserMapper(null)) - .withMessage("oidcUserMapper cannot be null"); - // @formatter:on - } - @Test public void loadUserWhenUserRequestIsNullThenThrowIllegalArgumentException() { assertThatIllegalArgumentException().isThrownBy(() -> this.userService.loadUser(null)); @@ -244,61 +216,6 @@ public void loadUserWhenStandardScopesAuthorizedThenUserInfoEndpointRequested() assertThat(user.getUserInfo()).isNotNull(); } - @Test - public void loadUserWhenCustomRetrieveUserInfoSetThenUsed() { - // @formatter:off - String userInfoResponse = "{\n" - + " \"sub\": \"subject1\",\n" - + " \"name\": \"first last\",\n" - + " \"given_name\": \"first\",\n" - + " \"family_name\": \"last\",\n" - + " \"preferred_username\": \"user1\",\n" - + " \"email\": \"user1@example.com\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(userInfoResponse)); - String userInfoUri = this.server.url("/user").toString(); - ClientRegistration clientRegistration = this.clientRegistrationBuilder.userInfoUri(userInfoUri).build(); - this.accessToken = TestOAuth2AccessTokens.noScopes(); - Predicate customRetrieveUserInfo = mock(Predicate.class); - given(customRetrieveUserInfo.test(any(OidcUserRequest.class))).willReturn(true); - this.userService.setRetrieveUserInfo(customRetrieveUserInfo); - OidcUser user = this.userService - .loadUser(new OidcUserRequest(clientRegistration, this.accessToken, this.idToken)); - assertThat(user.getUserInfo()).isNotNull(); - } - - @Test - public void loadUserWhenCustomOidcUserMapperSetThenUsed() { - // @formatter:off - String userInfoResponse = "{\n" - + " \"sub\": \"subject1\",\n" - + " \"name\": \"first last\",\n" - + " \"given_name\": \"first\",\n" - + " \"family_name\": \"last\",\n" - + " \"preferred_username\": \"user1\",\n" - + " \"email\": \"user1@example.com\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(userInfoResponse)); - String userInfoUri = this.server.url("/user").toString(); - ClientRegistration clientRegistration = this.clientRegistrationBuilder.userInfoUri(userInfoUri).build(); - this.accessToken = TestOAuth2AccessTokens.noScopes(); - BiFunction customOidcUserMapper = mock(BiFunction.class); - OidcUser actualUser = new DefaultOidcUser(AuthorityUtils.createAuthorityList("a", "b"), this.idToken, - IdTokenClaimNames.SUB); - given(customOidcUserMapper.apply(any(OidcUserRequest.class), any(OidcUserInfo.class))).willReturn(actualUser); - this.userService.setOidcUserMapper(customOidcUserMapper); - OidcUserRequest userRequest = new OidcUserRequest(clientRegistration, this.accessToken, this.idToken); - OidcUser user = this.userService.loadUser(userRequest); - assertThat(user).isEqualTo(actualUser); - ArgumentCaptor userInfoCaptor = ArgumentCaptor.forClass(OidcUserInfo.class); - verify(customOidcUserMapper).apply(eq(userRequest), userInfoCaptor.capture()); - OidcUserInfo userInfo = userInfoCaptor.getValue(); - assertThat(userInfo.getSubject()).isEqualTo("subject1"); - assertThat(userInfo.getClaimAsString("preferred_username")).isEqualTo("user1"); - } - @Test public void loadUserWhenUserInfoSuccessResponseThenReturnUser() { // @formatter:off @@ -575,49 +492,6 @@ public void loadUserWhenTokenDoesNotContainScopesAndUserInfoUriThenUserInfoReque assertThat(user.getUserInfo()).isNotNull(); } - @Test - public void loadUserWhenNestedUserInfoSuccessThenReturnUser() { - // @formatter:off - String userInfoResponse = "{\n" - + " \"user\": {\"user-name\": \"user1\"},\n" - + " \"sub\" : \"subject1\",\n" - + " \"first-name\": \"first\",\n" - + " \"last-name\": \"last\",\n" - + " \"middle-name\": \"middle\",\n" - + " \"address\": \"address\",\n" - + " \"email\": \"user1@example.com\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(userInfoResponse)); - String userInfoUri = this.server.url("/user").toString(); - ClientRegistration clientRegistration = this.clientRegistrationBuilder.userInfoUri(userInfoUri) - .userInfoAuthenticationMethod(AuthenticationMethod.HEADER) - .userNameAttributeName("user-name") - .build(); - OidcUserService userService = new OidcUserService(); - DefaultOAuth2UserService oAuth2UserService = new DefaultOAuth2UserService(); - oAuth2UserService.setAttributesConverter((request) -> (attributes) -> { - Map user = (Map) attributes.get("user"); - attributes.put("user-name", user.get("user-name")); - return attributes; - }); - userService.setOauth2UserService(oAuth2UserService); - OAuth2User user = userService.loadUser(new OidcUserRequest(clientRegistration, this.accessToken, this.idToken)); - assertThat(user.getName()).isEqualTo("user1"); - assertThat(user.getAttributes()).hasSize(9); - assertThat(((Map) user.getAttribute("user")).get("user-name")).isEqualTo("user1"); - assertThat((String) user.getAttribute("first-name")).isEqualTo("first"); - assertThat((String) user.getAttribute("last-name")).isEqualTo("last"); - assertThat((String) user.getAttribute("middle-name")).isEqualTo("middle"); - assertThat((String) user.getAttribute("address")).isEqualTo("address"); - assertThat((String) user.getAttribute("email")).isEqualTo("user1@example.com"); - assertThat(user.getAuthorities()).hasSize(3); - assertThat(user.getAuthorities().iterator().next()).isInstanceOf(OAuth2UserAuthority.class); - OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); - assertThat(userAuthority.getAuthority()).isEqualTo("OIDC_USER"); - assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); - } - private MockResponse jsonResponse(String json) { // @formatter:off return new MockResponse() diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java index 99457d4e3e7..361100ec6f3 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -158,46 +158,6 @@ public void loadUserWhenUserInfoSuccessResponseThenReturnUser() { assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); } - @Test - public void loadUserWhenNestedUserInfoSuccessThenReturnUser() { - // @formatter:off - String userInfoResponse = "{\n" - + " \"user\": {\"user-name\": \"user1\"},\n" - + " \"first-name\": \"first\",\n" - + " \"last-name\": \"last\",\n" - + " \"middle-name\": \"middle\",\n" - + " \"address\": \"address\",\n" - + " \"email\": \"user1@example.com\"\n" - + "}\n"; - // @formatter:on - this.server.enqueue(jsonResponse(userInfoResponse)); - String userInfoUri = this.server.url("/user").toString(); - ClientRegistration clientRegistration = this.clientRegistrationBuilder.userInfoUri(userInfoUri) - .userInfoAuthenticationMethod(AuthenticationMethod.HEADER) - .userNameAttributeName("user-name") - .build(); - DefaultOAuth2UserService userService = new DefaultOAuth2UserService(); - userService.setAttributesConverter((request) -> (attributes) -> { - Map user = (Map) attributes.get("user"); - attributes.put("user-name", user.get("user-name")); - return attributes; - }); - OAuth2User user = userService.loadUser(new OAuth2UserRequest(clientRegistration, this.accessToken)); - assertThat(user.getName()).isEqualTo("user1"); - assertThat(user.getAttributes()).hasSize(7); - assertThat(((Map) user.getAttribute("user")).get("user-name")).isEqualTo("user1"); - assertThat((String) user.getAttribute("first-name")).isEqualTo("first"); - assertThat((String) user.getAttribute("last-name")).isEqualTo("last"); - assertThat((String) user.getAttribute("middle-name")).isEqualTo("middle"); - assertThat((String) user.getAttribute("address")).isEqualTo("address"); - assertThat((String) user.getAttribute("email")).isEqualTo("user1@example.com"); - assertThat(user.getAuthorities()).hasSize(1); - assertThat(user.getAuthorities().iterator().next()).isInstanceOf(OAuth2UserAuthority.class); - OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); - assertThat(userAuthority.getAuthority()).isEqualTo("OAUTH2_USER"); - assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); - } - @Test public void loadUserWhenUserInfoSuccessResponseInvalidThenThrowOAuth2AuthenticationException() { // @formatter:off @@ -413,12 +373,6 @@ public void loadUserWhenUserInfoSuccessResponseInvalidContentTypeThenThrowOAuth2 + "from '" + userInfoUri + "': response contains invalid content type 'text/plain'."); } - @Test - public void setAttributesConverterWhenNullThenException() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> this.userService.setAttributesConverter(null)); - } - private DefaultOAuth2UserService withMockResponse(Map response) { ResponseEntity> responseEntity = new ResponseEntity<>(response, HttpStatus.OK); Converter> requestEntityConverter = mock(Converter.class); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java index 68aa1a31d3a..c9989ae3202 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -165,46 +165,6 @@ public void loadUserWhenUserInfo201CreatedResponseThenReturnUser() { assertThatNoException().isThrownBy(() -> this.userService.loadUser(oauth2UserRequest()).block()); } - @Test - public void loadUserWhenNestedUserInfoSuccessThenReturnUser() { - // @formatter:off - String userInfoResponse = "{\n" - + " \"user\": {\"user-name\": \"user1\"},\n" - + " \"first-name\": \"first\",\n" - + " \"last-name\": \"last\",\n" - + " \"middle-name\": \"middle\",\n" - + " \"address\": \"address\",\n" - + " \"email\": \"user1@example.com\"\n" - + "}\n"; - // @formatter:on - enqueueApplicationJsonBody(userInfoResponse); - String userInfoUri = this.server.url("/user").toString(); - ClientRegistration clientRegistration = this.clientRegistration.userInfoUri(userInfoUri) - .userInfoAuthenticationMethod(AuthenticationMethod.HEADER) - .userNameAttributeName("user-name") - .build(); - DefaultReactiveOAuth2UserService userService = new DefaultReactiveOAuth2UserService(); - userService.setAttributesConverter((request) -> (attributes) -> { - Map user = (Map) attributes.get("user"); - attributes.put("user-name", user.get("user-name")); - return attributes; - }); - OAuth2User user = userService.loadUser(new OAuth2UserRequest(clientRegistration, this.accessToken)).block(); - assertThat(user.getName()).isEqualTo("user1"); - assertThat(user.getAttributes()).hasSize(7); - assertThat(((Map) user.getAttribute("user")).get("user-name")).isEqualTo("user1"); - assertThat((String) user.getAttribute("first-name")).isEqualTo("first"); - assertThat((String) user.getAttribute("last-name")).isEqualTo("last"); - assertThat((String) user.getAttribute("middle-name")).isEqualTo("middle"); - assertThat((String) user.getAttribute("address")).isEqualTo("address"); - assertThat((String) user.getAttribute("email")).isEqualTo("user1@example.com"); - assertThat(user.getAuthorities()).hasSize(1); - assertThat(user.getAuthorities().iterator().next()).isInstanceOf(OAuth2UserAuthority.class); - OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); - assertThat(userAuthority.getAuthority()).isEqualTo("OAUTH2_USER"); - assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); - } - // gh-5500 @Test public void loadUserWhenAuthenticationMethodHeaderSuccessResponseThenHttpMethodGet() throws Exception { @@ -330,12 +290,6 @@ public void loadUserWhenUserInfoSuccessResponseInvalidContentTypeThenThrowOAuth2 + "response contains invalid content type 'text/plain'"); } - @Test - public void setAttributesConverterWhenNullThenException() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> this.userService.setAttributesConverter(null)); - } - private DefaultReactiveOAuth2UserService withMockResponse(Map body) { WebClient real = WebClient.builder().build(); WebClient.RequestHeadersUriSpec spec = spy(real.post()); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java index 59676b14619..7123f0169e7 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -119,11 +119,6 @@ public void setRequestCacheWhenRequestCacheIsNullThenThrowIllegalArgumentExcepti assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequestCache(null)); } - @Test - public void setAuthenticationFailureHandlerIsNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)); - } - @Test public void doFilterWhenNotAuthorizationRequestThenNextFilter() throws Exception { String requestUri = "/path"; @@ -149,31 +144,6 @@ public void doFilterWhenAuthorizationRequestWithInvalidClientThenStatusInternalS assertThat(response.getErrorMessage()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()); } - @Test - public void doFilterWhenAuthorizationRequestWithInvalidClientAndCustomFailureHandlerThenCustomError() - throws Exception { - String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/" - + this.registration1.getRegistrationId() + "-invalid"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.setAuthenticationFailureHandler((request1, response1, ex) -> { - Throwable cause = ex.getCause(); - if (InvalidClientRegistrationIdException.class.isAssignableFrom(cause.getClass())) { - response1.sendError(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase()); - } - else { - response1.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(), - HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()); - } - }); - this.filter.doFilter(request, response, filterChain); - verifyNoMoreInteractions(filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(response.getErrorMessage()).isEqualTo(HttpStatus.BAD_REQUEST.getReasonPhrase()); - } - @Test public void doFilterWhenAuthorizationRequestOAuth2LoginThenRedirectForAuthorization() throws Exception { String requestUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/" diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java index e1321bd7595..07b16e2b741 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java @@ -69,12 +69,6 @@ public final class AuthorizationGrantType implements Serializable { public static final AuthorizationGrantType DEVICE_CODE = new AuthorizationGrantType( "urn:ietf:params:oauth:grant-type:device_code"); - /** - * @since 6.3 - */ - public static final AuthorizationGrantType TOKEN_EXCHANGE = new AuthorizationGrantType( - "urn:ietf:params:oauth:grant-type:token-exchange"); - private final String value; /** diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ClientAuthenticationMethod.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ClientAuthenticationMethod.java index 5dbf88cd856..0b986dcc552 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ClientAuthenticationMethod.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ClientAuthenticationMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -62,17 +62,6 @@ public final class ClientAuthenticationMethod implements Serializable { */ public static final ClientAuthenticationMethod NONE = new ClientAuthenticationMethod("none"); - /** - * @since 6.3 - */ - public static final ClientAuthenticationMethod TLS_CLIENT_AUTH = new ClientAuthenticationMethod("tls_client_auth"); - - /** - * @since 6.3 - */ - public static final ClientAuthenticationMethod SELF_SIGNED_TLS_CLIENT_AUTH = new ClientAuthenticationMethod( - "self_signed_tls_client_auth"); - private final String value; /** diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2ParameterNames.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2ParameterNames.java index 49a3fa933f3..d387b482d94 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2ParameterNames.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2ParameterNames.java @@ -182,54 +182,6 @@ public final class OAuth2ParameterNames { */ public static final String INTERVAL = "interval"; - /** - * {@code audience} - used in Token Exchange Access Token Request. - * @since 6.3 - */ - public static final String AUDIENCE = "audience"; - - /** - * {@code resource} - used in Token Exchange Access Token Request. - * @since 6.3 - */ - public static final String RESOURCE = "resource"; - - /** - * {@code requested_token_type} - used in Token Exchange Access Token Request. - * @since 6.3 - */ - public static final String REQUESTED_TOKEN_TYPE = "requested_token_type"; - - /** - * {@code issued_token_type} - used in Token Exchange Access Token Response. - * @since 6.3 - */ - public static final String ISSUED_TOKEN_TYPE = "issued_token_type"; - - /** - * {@code subject_token} - used in Token Exchange Access Token Request. - * @since 6.3 - */ - public static final String SUBJECT_TOKEN = "subject_token"; - - /** - * {@code subject_token_type} - used in Token Exchange Access Token Request. - * @since 6.3 - */ - public static final String SUBJECT_TOKEN_TYPE = "subject_token_type"; - - /** - * {@code actor_token} - used in Token Exchange Access Token Request. - * @since 6.3 - */ - public static final String ACTOR_TOKEN = "actor_token"; - - /** - * {@code actor_token_type} - used in Token Exchange Access Token Request. - * @since 6.3 - */ - public static final String ACTOR_TOKEN_TYPE = "actor_token_type"; - private OAuth2ParameterNames() { } diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/ClientAuthenticationMethodTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/ClientAuthenticationMethodTests.java index 0e8353e1030..373b6277c66 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/ClientAuthenticationMethodTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/ClientAuthenticationMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -58,15 +58,4 @@ public void getValueWhenAuthenticationMethodNoneThenReturnNone() { assertThat(ClientAuthenticationMethod.NONE.getValue()).isEqualTo("none"); } - @Test - public void getValueWhenAuthenticationMethodTlsClientAuthThenReturnTlsClientAuth() { - assertThat(ClientAuthenticationMethod.TLS_CLIENT_AUTH.getValue()).isEqualTo("tls_client_auth"); - } - - @Test - public void getValueWhenAuthenticationMethodSelfSignedTlsClientAuthThenReturnSelfSignedTlsClientAuth() { - assertThat(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH.getValue()) - .isEqualTo("self_signed_tls_client_auth"); - } - } diff --git a/oauth2/oauth2-jose/spring-security-oauth2-jose.gradle b/oauth2/oauth2-jose/spring-security-oauth2-jose.gradle index 3f449f18e6d..8290b8579ee 100644 --- a/oauth2/oauth2-jose/spring-security-oauth2-jose.gradle +++ b/oauth2/oauth2-jose/spring-security-oauth2-jose.gradle @@ -10,9 +10,6 @@ dependencies { optional 'io.projectreactor:reactor-core' optional 'org.springframework:spring-webflux' - testImplementation "org.bouncycastle:bcpkix-jdk18on" - testImplementation "org.bouncycastle:bcprov-jdk18on" - testImplementation "jakarta.servlet:jakarta.servlet-api" testImplementation 'com.squareup.okhttp3:mockwebserver' testImplementation 'io.projectreactor.netty:reactor-netty' testImplementation 'com.fasterxml.jackson.core:jackson-databind' diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderProviderConfigurationUtils.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderProviderConfigurationUtils.java index 36ca9b68689..47a068dd751 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderProviderConfigurationUtils.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderProviderConfigurationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -39,7 +39,6 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; -import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.util.Assert; import org.springframework.web.client.HttpClientErrorException; @@ -66,15 +65,6 @@ final class JwtDecoderProviderConfigurationUtils { private static final RestTemplate rest = new RestTemplate(); - static { - int connectTimeout = Integer.parseInt(System.getProperty("sun.net.client.defaultConnectTimeout", "30000")); - int readTimeout = Integer.parseInt(System.getProperty("sun.net.client.defaultReadTimeout", "30000")); - SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); - requestFactory.setConnectTimeout(connectTimeout); - requestFactory.setReadTimeout(readTimeout); - rest.setRequestFactory(requestFactory); - } - private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference>() { }; diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java index 8c2fa20909b..4d13ce52ab2 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,6 @@ import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; /** * Provides factory methods for creating {@code OAuth2TokenValidator} @@ -52,7 +50,10 @@ private JwtValidators() { * supplied */ public static OAuth2TokenValidator createDefaultWithIssuer(String issuer) { - return createDefaultWithValidators(new JwtIssuerValidator(issuer)); + List> validators = new ArrayList<>(); + validators.add(new JwtTimestampValidator()); + validators.add(new JwtIssuerValidator(issuer)); + return new DelegatingOAuth2TokenValidator<>(validators); } /** @@ -68,52 +69,7 @@ public static OAuth2TokenValidator createDefaultWithIssuer(String issuer) { * supplied */ public static OAuth2TokenValidator createDefault() { - return new DelegatingOAuth2TokenValidator<>( - Arrays.asList(new JwtTimestampValidator(), new X509CertificateThumbprintValidator( - X509CertificateThumbprintValidator.DEFAULT_X509_CERTIFICATE_SUPPLIER))); - } - - /** - *

    - * Create a {@link Jwt} default validator with standard validators and additional - * validators. - *

    - * @param validators additional validators - * @return - a delegating validator containing all standard validators with additional - * validators - * @since 6.3 - */ - public static OAuth2TokenValidator createDefaultWithValidators(List> validators) { - Assert.notEmpty(validators, "validators cannot be null or empty"); - List> tokenValidators = new ArrayList<>(validators); - X509CertificateThumbprintValidator x509CertificateThumbprintValidator = CollectionUtils - .findValueOfType(tokenValidators, X509CertificateThumbprintValidator.class); - if (x509CertificateThumbprintValidator == null) { - tokenValidators.add(0, new X509CertificateThumbprintValidator( - X509CertificateThumbprintValidator.DEFAULT_X509_CERTIFICATE_SUPPLIER)); - } - JwtTimestampValidator jwtTimestampValidator = CollectionUtils.findValueOfType(tokenValidators, - JwtTimestampValidator.class); - if (jwtTimestampValidator == null) { - tokenValidators.add(0, new JwtTimestampValidator()); - } - return new DelegatingOAuth2TokenValidator<>(tokenValidators); - } - - /** - *

    - * Create a {@link Jwt} default validator with standard validators and additional - * validators. - *

    - * @param validators additional validators - * @return - a delegating validator containing all standard validators with additional - * validators - * @since 6.3 - */ - public static OAuth2TokenValidator createDefaultWithValidators(OAuth2TokenValidator... validators) { - Assert.notEmpty(validators, "validators cannot be null or empty"); - List> tokenValidators = new ArrayList<>(Arrays.asList(validators)); - return createDefaultWithValidators(tokenValidators); + return new DelegatingOAuth2TokenValidator<>(Arrays.asList(new JwtTimestampValidator())); } } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/X509CertificateThumbprintValidator.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/X509CertificateThumbprintValidator.java deleted file mode 100644 index 1344d952a43..00000000000 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/X509CertificateThumbprintValidator.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.jwt; - -import java.security.MessageDigest; -import java.security.cert.X509Certificate; -import java.util.Base64; -import java.util.Map; -import java.util.function.Supplier; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -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.core.OAuth2TokenValidatorResult; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.RequestContextHolder; - -/** - * An {@link OAuth2TokenValidator} responsible for validating the {@code x5t#S256} claim - * (if available) in the {@link Jwt} against the SHA-256 Thumbprint of the supplied - * {@code X509Certificate}. - * - * @author Joe Grandja - * @since 6.3 - * @see OAuth2TokenValidator - * @see Jwt - * @see 3. Mutual-TLS Client - * Certificate-Bound Access Tokens - * @see 3.1. JWT Certificate - * Thumbprint Confirmation Method - */ -final class X509CertificateThumbprintValidator implements OAuth2TokenValidator { - - static final Supplier DEFAULT_X509_CERTIFICATE_SUPPLIER = new DefaultX509CertificateSupplier(); - - private final Log logger = LogFactory.getLog(getClass()); - - private final Supplier x509CertificateSupplier; - - X509CertificateThumbprintValidator(Supplier x509CertificateSupplier) { - Assert.notNull(x509CertificateSupplier, "x509CertificateSupplier cannot be null"); - this.x509CertificateSupplier = x509CertificateSupplier; - } - - @Override - public OAuth2TokenValidatorResult validate(Jwt jwt) { - Map confirmationMethodClaim = jwt.getClaim("cnf"); - String x509CertificateThumbprintClaim = null; - if (!CollectionUtils.isEmpty(confirmationMethodClaim) && confirmationMethodClaim.containsKey("x5t#S256")) { - x509CertificateThumbprintClaim = (String) confirmationMethodClaim.get("x5t#S256"); - } - if (x509CertificateThumbprintClaim == null) { - return OAuth2TokenValidatorResult.success(); - } - - X509Certificate x509Certificate = this.x509CertificateSupplier.get(); - if (x509Certificate == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, - "Unable to obtain X509Certificate from current request.", null); - if (this.logger.isDebugEnabled()) { - this.logger.debug(error.toString()); - } - return OAuth2TokenValidatorResult.failure(error); - } - - String x509CertificateThumbprint; - try { - x509CertificateThumbprint = computeSHA256Thumbprint(x509Certificate); - } - catch (Exception ex) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, - "Failed to compute SHA-256 Thumbprint for X509Certificate.", null); - if (this.logger.isDebugEnabled()) { - this.logger.debug(error.toString()); - } - return OAuth2TokenValidatorResult.failure(error); - } - - if (!x509CertificateThumbprint.equals(x509CertificateThumbprintClaim)) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, - "Invalid SHA-256 Thumbprint for X509Certificate.", null); - if (this.logger.isDebugEnabled()) { - this.logger.debug(error.toString()); - } - return OAuth2TokenValidatorResult.failure(error); - } - - return OAuth2TokenValidatorResult.success(); - } - - 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); - } - - private static final class DefaultX509CertificateSupplier implements Supplier { - - @Override - public X509Certificate get() { - RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); - if (requestAttributes == null) { - return null; - } - - X509Certificate[] clientCertificateChain = (X509Certificate[]) requestAttributes - .getAttribute("jakarta.servlet.request.X509Certificate", RequestAttributes.SCOPE_REQUEST); - - return (clientCertificateChain != null && clientCertificateChain.length > 0) ? clientCertificateChain[0] - : null; - } - - } - -} diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jose/TestX509Certificates.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jose/TestX509Certificates.java deleted file mode 100644 index 9e5a0740f05..00000000000 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jose/TestX509Certificates.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.jose; - -import java.security.KeyPair; -import java.security.cert.X509Certificate; - -/** - * @author Joe Grandja - * @since 6.3 - */ -public final class TestX509Certificates { - - public static final X509Certificate[] DEFAULT_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 subject1 - KeyPair subject1KeyPair = X509CertificateUtils.generateRSAKeyPair(); - distinguishedName = "CN=subject1, OU=Spring Samples, O=Spring, C=US"; - X509Certificate subject1Certificate = X509CertificateUtils.createEndEntityCertificate(caCertificate, - caKeyPair.getPrivate(), subject1KeyPair.getPublic(), distinguishedName); - - DEFAULT_PKI_CERTIFICATE = new X509Certificate[] { subject1Certificate, caCertificate, rootCertificate }; - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - public static final X509Certificate[] DEFAULT_SELF_SIGNED_CERTIFICATE; - static { - try { - // Generate self-signed certificate for subject1 - KeyPair keyPair = X509CertificateUtils.generateRSAKeyPair(); - String distinguishedName = "CN=subject1, OU=Spring Samples, O=Spring, C=US"; - X509Certificate subject1SelfSignedCertificate = X509CertificateUtils.createTrustAnchorCertificate(keyPair, - distinguishedName); - - DEFAULT_SELF_SIGNED_CERTIFICATE = new X509Certificate[] { subject1SelfSignedCertificate }; - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - private TestX509Certificates() { - } - -} diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jose/X509CertificateUtils.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jose/X509CertificateUtils.java deleted file mode 100644 index 873251628dc..00000000000 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jose/X509CertificateUtils.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.jose; - -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 - * @since 6.3 - */ -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() { - } - - 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; - } - - 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)); - } - - 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)); - } - - 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/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtValidatorsTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtValidatorsTests.java deleted file mode 100644 index 5c4cf6897c1..00000000000 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtValidatorsTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.jwt; - -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; -import org.springframework.security.oauth2.core.OAuth2TokenValidator; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.util.CollectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatException; - -/** - * Tests for {@link JwtValidators}. - * - * @author Max Batischev - */ -public class JwtValidatorsTests { - - private static final String ISS_CLAIM = "iss"; - - @Test - public void createWhenJwtIssuerValidatorIsPresentThenCreateDefaultValidatorWithJwtIssuerValidator() { - OAuth2TokenValidator validator = JwtValidators - .createDefaultWithValidators(new JwtIssuerValidator(ISS_CLAIM)); - - assertThat(containsByType(validator, JwtIssuerValidator.class)).isTrue(); - assertThat(containsByType(validator, JwtTimestampValidator.class)).isTrue(); - assertThat(containsByType(validator, X509CertificateThumbprintValidator.class)).isTrue(); - } - - @Test - @SuppressWarnings("unchecked") - public void createWhenJwtTimestampValidatorIsPresentThenCreateDefaultValidatorWithOnlyOneJwtTimestampValidator() { - OAuth2TokenValidator validator = JwtValidators.createDefaultWithValidators(new JwtTimestampValidator()); - - DelegatingOAuth2TokenValidator delegatingOAuth2TokenValidator = (DelegatingOAuth2TokenValidator) validator; - Collection> tokenValidators = (Collection>) ReflectionTestUtils - .getField(delegatingOAuth2TokenValidator, "tokenValidators"); - - assertThat(containsByType(validator, JwtTimestampValidator.class)).isTrue(); - assertThat(containsByType(validator, X509CertificateThumbprintValidator.class)).isTrue(); - assertThat(Objects.requireNonNull(tokenValidators).size()).isEqualTo(2); - } - - @Test - public void createWhenEmptyValidatorsThenThrowsException() { - assertThatException().isThrownBy(() -> JwtValidators.createDefaultWithValidators(Collections.emptyList())); - } - - @SuppressWarnings("unchecked") - private boolean containsByType(OAuth2TokenValidator validator, Class> type) { - DelegatingOAuth2TokenValidator delegatingOAuth2TokenValidator = (DelegatingOAuth2TokenValidator) validator; - Collection> tokenValidators = (Collection>) ReflectionTestUtils - .getField(delegatingOAuth2TokenValidator, "tokenValidators"); - - OAuth2TokenValidator tokenValidator = CollectionUtils - .findValueOfType(Objects.requireNonNull(tokenValidators), type); - return tokenValidator != null; - } - -} diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/X509CertificateThumbprintValidatorTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/X509CertificateThumbprintValidatorTests.java deleted file mode 100644 index cd6880191d3..00000000000 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/X509CertificateThumbprintValidatorTests.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.jwt; - -import java.util.Collections; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.jose.TestX509Certificates; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * @author Joe Grandja - * @since 6.3 - */ -class X509CertificateThumbprintValidatorTests { - - private final X509CertificateThumbprintValidator validator = new X509CertificateThumbprintValidator( - X509CertificateThumbprintValidator.DEFAULT_X509_CERTIFICATE_SUPPLIER); - - @AfterEach - void cleanup() { - RequestContextHolder.resetRequestAttributes(); - } - - @Test - void constructorWhenX509CertificateSupplierNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new X509CertificateThumbprintValidator(null)).withMessage("x509CertificateSupplier cannot be null"); - // @formatter:on - } - - @Test - void validateWhenCnfClaimNotAvailableThenSuccess() { - Jwt jwt = TestJwts.jwt().build(); - assertThat(this.validator.validate(jwt).hasErrors()).isFalse(); - } - - @Test - void validateWhenX5tClaimNotAvailableThenSuccess() { - // @formatter:off - Jwt jwt = TestJwts.jwt() - .claim("cnf", Collections.emptyMap()) - .build(); - // @formatter:on - assertThat(this.validator.validate(jwt).hasErrors()).isFalse(); - } - - @Test - void validateWhenX509CertificateMissingThenHasErrors() throws Exception { - String sha256Thumbprint = X509CertificateThumbprintValidator - .computeSHA256Thumbprint(TestX509Certificates.DEFAULT_PKI_CERTIFICATE[0]); - // @formatter:off - Jwt jwt = TestJwts.jwt() - .claim("cnf", Collections.singletonMap("x5t#S256", sha256Thumbprint)) - .build(); - // @formatter:on - - // @formatter:off - assertThat(this.validator.validate(jwt).getErrors()) - .hasSize(1) - .first() - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - assertThat(error.getDescription()).isEqualTo("Unable to obtain X509Certificate from current request."); - }); - // @formatter:on - } - - @Test - void validateWhenX509CertificateThumbprintInvalidThenHasErrors() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute("jakarta.servlet.request.X509Certificate", - TestX509Certificates.DEFAULT_SELF_SIGNED_CERTIFICATE); - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request, null)); - - String sha256Thumbprint = X509CertificateThumbprintValidator - .computeSHA256Thumbprint(TestX509Certificates.DEFAULT_PKI_CERTIFICATE[0]); - // @formatter:off - Jwt jwt = TestJwts.jwt() - .claim("cnf", Collections.singletonMap("x5t#S256", sha256Thumbprint)) - .build(); - // @formatter:on - - // @formatter:off - assertThat(this.validator.validate(jwt).getErrors()) - .hasSize(1) - .first() - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - assertThat(error.getDescription()).isEqualTo("Invalid SHA-256 Thumbprint for X509Certificate."); - }); - // @formatter:on - } - - @Test - void validateWhenX509CertificateThumbprintValidThenSuccess() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute("jakarta.servlet.request.X509Certificate", TestX509Certificates.DEFAULT_PKI_CERTIFICATE); - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request, null)); - - String sha256Thumbprint = X509CertificateThumbprintValidator - .computeSHA256Thumbprint(TestX509Certificates.DEFAULT_PKI_CERTIFICATE[0]); - // @formatter:off - Jwt jwt = TestJwts.jwt() - .claim("cnf", Collections.singletonMap("x5t#S256", sha256Thumbprint)) - .build(); - // @formatter:on - - assertThat(this.validator.validate(jwt).hasErrors()).isFalse(); - } - -} diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java index c289ff294a2..51bf54592eb 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -23,7 +23,6 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; @@ -41,7 +40,6 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor; import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; @@ -71,8 +69,6 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private Converter> requestEntityConverter; - private Converter authenticationConverter = this::defaultAuthenticationConverter; - /** * Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters * @param introspectionUri The introspection endpoint uri @@ -91,6 +87,7 @@ public SpringOpaqueTokenIntrospector(String introspectionUri, String clientId, S /** * Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters + * * The given {@link RestOperations} should perform its own client authentication * against the introspection endpoint. * @param introspectionUri The introspection endpoint uri @@ -131,8 +128,7 @@ public OAuth2AuthenticatedPrincipal introspect(String token) { } ResponseEntity> responseEntity = makeRequest(requestEntity); Map claims = adaptToNimbusResponse(responseEntity); - OAuth2TokenIntrospectionClaimAccessor accessor = convertClaimsSet(claims); - return this.authenticationConverter.convert(accessor); + return convertClaimsSet(claims); } /** @@ -183,7 +179,7 @@ private Map adaptToNimbusResponse(ResponseEntity claims) { + private OAuth2AuthenticatedPrincipal convertClaimsSet(Map claims) { Map converted = new LinkedHashMap<>(claims); converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> { if (v instanceof String) { @@ -217,64 +213,18 @@ private OAuth2TokenIntrospectionClaimAccessor convertClaimsSet(Map v.toString()); converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF, (k, v) -> Instant.ofEpochSecond(((Number) v).longValue())); - converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, - (k, v) -> (v instanceof String s) ? new ArrayListFromString(s.split(" ")) : v); - return () -> converted; - } - - /** - *

    - * Sets the {@link Converter Converter<OAuth2TokenIntrospectionClaimAccessor, - * OAuth2AuthenticatedPrincipal>} to use. Defaults to - * {@link SpringOpaqueTokenIntrospector#defaultAuthenticationConverter}. - *

    - *

    - * Use if you need a custom mapping of OAuth 2.0 token claims to the authenticated - * principal. - *

    - * @param authenticationConverter the converter - * @since 6.3 - */ - public void setAuthenticationConverter( - Converter authenticationConverter) { - Assert.notNull(authenticationConverter, "converter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * If {@link SpringOpaqueTokenIntrospector#authenticationConverter} is not explicitly - * set, this default converter will be used. transforms an - * {@link OAuth2TokenIntrospectionClaimAccessor} into an - * {@link OAuth2AuthenticatedPrincipal} by extracting claims, mapping scopes to - * authorities, and creating a principal. - * @return {@link Converter Converter<OAuth2TokenIntrospectionClaimAccessor, - * OAuth2AuthenticatedPrincipal>} - * @since 6.3 - */ - private OAuth2IntrospectionAuthenticatedPrincipal defaultAuthenticationConverter( - OAuth2TokenIntrospectionClaimAccessor accessor) { - Collection authorities = authorities(accessor.getScopes()); - return new OAuth2IntrospectionAuthenticatedPrincipal(accessor.getClaims(), authorities); - } - - private Collection authorities(List scopes) { - if (!(scopes instanceof ArrayListFromString)) { - return Collections.emptyList(); - } Collection authorities = new ArrayList<>(); - for (String scope : scopes) { - authorities.add(new SimpleGrantedAuthority(AUTHORITY_PREFIX + scope)); - } - return authorities; - } - - // gh-7563 - private static final class ArrayListFromString extends ArrayList { - - ArrayListFromString(String... elements) { - super(Arrays.asList(elements)); - } - + converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> { + if (v instanceof String) { + Collection scopes = Arrays.asList(((String) v).split(" ")); + for (String scope : scopes) { + authorities.add(new SimpleGrantedAuthority(AUTHORITY_PREFIX + scope)); + } + return scopes; + } + return v; + }); + return new OAuth2IntrospectionAuthenticatedPrincipal(converted, authorities); } } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java index 98fae18dfda..f6b2ceb8e02 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -23,13 +23,11 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import reactor.core.publisher.Mono; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; @@ -38,7 +36,6 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor; import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; import org.springframework.util.Assert; import org.springframework.web.reactive.function.BodyInserters; @@ -65,8 +62,6 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke private final WebClient webClient; - private Converter> authenticationConverter = this::defaultAuthenticationConverter; - /** * Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided * parameters @@ -102,8 +97,6 @@ public Mono introspect(String token) { .flatMap(this::makeRequest) .flatMap(this::adaptToNimbusResponse) .map(this::convertClaimsSet) - .flatMap(this.authenticationConverter::convert) - .cast(OAuth2AuthenticatedPrincipal.class) .onErrorMap((e) -> !(e instanceof OAuth2IntrospectionException), this::onError); // @formatter:on } @@ -143,7 +136,7 @@ private Mono> adaptToNimbusResponse(ClientResponse responseE .switchIfEmpty(Mono.error(() -> new BadOpaqueTokenException("Provided token isn't active"))); } - private OAuth2TokenIntrospectionClaimAccessor convertClaimsSet(Map claims) { + private OAuth2AuthenticatedPrincipal convertClaimsSet(Map claims) { Map converted = new LinkedHashMap<>(claims); converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> { if (v instanceof String) { @@ -177,58 +170,22 @@ private OAuth2TokenIntrospectionClaimAccessor convertClaimsSet(Map v.toString()); converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF, (k, v) -> Instant.ofEpochSecond(((Number) v).longValue())); - converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, - (k, v) -> (v instanceof String s) ? new ArrayListFromString(s.split(" ")) : v); - return () -> converted; + Collection authorities = new ArrayList<>(); + converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> { + if (v instanceof String) { + Collection scopes = Arrays.asList(((String) v).split(" ")); + for (String scope : scopes) { + authorities.add(new SimpleGrantedAuthority(AUTHORITY_PREFIX + scope)); + } + return scopes; + } + return v; + }); + return new OAuth2IntrospectionAuthenticatedPrincipal(converted, authorities); } private OAuth2IntrospectionException onError(Throwable ex) { return new OAuth2IntrospectionException(ex.getMessage(), ex); } - /** - *

    - * Sets the {@link Converter Converter<OAuth2TokenIntrospectionClaimAccessor, - * OAuth2AuthenticatedPrincipal>} to use. Defaults to - * {@link SpringReactiveOpaqueTokenIntrospector#defaultAuthenticationConverter}. - *

    - *

    - * Use if you need a custom mapping of OAuth 2.0 token claims to the authenticated - * principal. - *

    - * @param authenticationConverter the converter - * @since 6.3 - */ - public void setAuthenticationConverter( - Converter> authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - private Mono defaultAuthenticationConverter( - OAuth2TokenIntrospectionClaimAccessor accessor) { - Collection authorities = authorities(accessor.getScopes()); - return Mono.just(new OAuth2IntrospectionAuthenticatedPrincipal(accessor.getClaims(), authorities)); - } - - private Collection authorities(List scopes) { - if (!(scopes instanceof ArrayListFromString)) { - return Collections.emptyList(); - } - Collection authorities = new ArrayList<>(); - for (String scope : scopes) { - authorities.add(new SimpleGrantedAuthority(AUTHORITY_PREFIX + scope)); - } - return authorities; - } - - // gh-7563 - private static final class ArrayListFromString extends ArrayList { - - ArrayListFromString(String... elements) { - super(Arrays.asList(elements)); - } - - } - } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java index 12c4c17d829..f46dbd1edb1 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -40,7 +40,6 @@ import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor; import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; import org.springframework.web.client.RestOperations; @@ -294,35 +293,6 @@ public void setRequestEntityConverterWhenNonNullConverterGivenThenConverterUsed( verify(requestEntityConverter).convert(tokenToIntrospect); } - @Test - public void setAuthenticationConverterWhenConverterIsNullThenExceptionIsThrown() { - RestOperations restOperations = mock(RestOperations.class); - SpringOpaqueTokenIntrospector introspectionClient = new SpringOpaqueTokenIntrospector(INTROSPECTION_URL, - restOperations); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> introspectionClient.setAuthenticationConverter(null)); - } - - @Test - public void setAuthenticationConverterWhenNonNullConverterGivenThenConverterUsed() { - RestOperations restOperations = mock(RestOperations.class); - Converter> requestEntityConverter = mock(Converter.class); - RequestEntity requestEntity = mock(RequestEntity.class); - Converter authenticationConverter = mock( - Converter.class); - OAuth2AuthenticatedPrincipal oAuth2AuthenticatedPrincipal = mock(OAuth2AuthenticatedPrincipal.class); - String tokenToIntrospect = "some token"; - given(requestEntityConverter.convert(tokenToIntrospect)).willReturn(requestEntity); - given(restOperations.exchange(requestEntity, STRING_OBJECT_MAP)).willReturn(response(ACTIVE_RESPONSE)); - given(authenticationConverter.convert(any())).willReturn(oAuth2AuthenticatedPrincipal); - SpringOpaqueTokenIntrospector introspectionClient = new SpringOpaqueTokenIntrospector(INTROSPECTION_URL, - restOperations); - introspectionClient.setRequestEntityConverter(requestEntityConverter); - introspectionClient.setAuthenticationConverter(authenticationConverter); - introspectionClient.introspect(tokenToIntrospect); - verify(authenticationConverter).convert(any()); - } - private static ResponseEntity> response(String content) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java index 9b5f0386dd7..f395fd63266 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -33,12 +33,10 @@ import reactor.core.publisher.Mono; import org.springframework.core.ParameterizedTypeReference; -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.security.oauth2.core.OAuth2AuthenticatedPrincipal; -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor; import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; @@ -46,11 +44,9 @@ 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.spy; -import static org.mockito.Mockito.verify; /** * Tests for {@link SpringReactiveOpaqueTokenIntrospector} @@ -197,30 +193,6 @@ public void authenticateWhenIntrospectionTokenReturnsInvalidResponseThenInvalidT // @formatter:on } - @Test - public void setAuthenticationConverterWhenConverterIsNullThenExceptionIsThrown() { - WebClient web = mock(WebClient.class); - SpringReactiveOpaqueTokenIntrospector introspectionClient = new SpringReactiveOpaqueTokenIntrospector( - INTROSPECTION_URL, web); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> introspectionClient.setAuthenticationConverter(null)); - } - - @Test - public void setAuthenticationConverterWhenNonNullConverterGivenThenConverterUsed() { - WebClient web = mockResponse(ACTIVE_RESPONSE); - Converter> authenticationConverter = mock( - Converter.class); - OAuth2AuthenticatedPrincipal oAuth2AuthenticatedPrincipal = mock(OAuth2AuthenticatedPrincipal.class); - String tokenToIntrospect = "some token"; - given(authenticationConverter.convert(any())).willReturn((Mono) Mono.just(oAuth2AuthenticatedPrincipal)); - SpringReactiveOpaqueTokenIntrospector introspectionClient = new SpringReactiveOpaqueTokenIntrospector( - INTROSPECTION_URL, web); - introspectionClient.setAuthenticationConverter(authenticationConverter); - introspectionClient.introspect(tokenToIntrospect).block(); - verify(authenticationConverter).convert(any()); - } - @Test public void constructorWhenIntrospectionUriIsEmptyThenIllegalArgumentException() { assertThatIllegalArgumentException() diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java index 723acfeba76..b195e4945a5 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java @@ -106,7 +106,7 @@ private void writeMetadataToResponse(HttpServletResponse response, Saml2Metadata response.setContentType(MediaType.APPLICATION_XML_VALUE); String format = "attachment; filename=\"%s\"; filename*=UTF-8''%s"; String fileName = metadata.getFileName(); - String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8); + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format(format, fileName, encodedFileName)); response.setContentLength(metadata.getMetadata().getBytes(StandardCharsets.UTF_8).length); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); diff --git a/settings.gradle b/settings.gradle index b2b73e99647..3caf677d61d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,7 +5,7 @@ pluginManagement { } plugins { - id "com.gradle.enterprise" version "3.16.2" + id "com.gradle.enterprise" version "3.12.6" id "io.spring.ge.conventions" version "0.0.16" } diff --git a/taglibs/src/main/resources/META-INF/security.tld b/taglibs/src/main/resources/META-INF/security.tld index 1fede78dff1..5aed63096f7 100644 --- a/taglibs/src/main/resources/META-INF/security.tld +++ b/taglibs/src/main/resources/META-INF/security.tld @@ -20,7 +20,7 @@ version="2.0"> Spring Security Authorization Tag Library - 6.3 + 6.2 security http://www.springframework.org/security/tags diff --git a/test/src/test/java/org/springframework/security/test/context/showcase/CustomUserDetails.java b/test/src/test/java/org/springframework/security/test/context/showcase/CustomUserDetails.java index 6bdb82cbadb..ccbc7a00f0f 100644 --- a/test/src/test/java/org/springframework/security/test/context/showcase/CustomUserDetails.java +++ b/test/src/test/java/org/springframework/security/test/context/showcase/CustomUserDetails.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,26 @@ public String getUsername() { return this.username; } + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + @Override public String toString() { return "CustomUserDetails{" + "username='" + this.username + '\'' + '}'; diff --git a/web/spring-security-web.gradle b/web/spring-security-web.gradle index 77cf908f45e..cf405549c2a 100644 --- a/web/spring-security-web.gradle +++ b/web/spring-security-web.gradle @@ -36,7 +36,6 @@ dependencies { testImplementation "org.mockito:mockito-core" testImplementation "org.mockito:mockito-junit-jupiter" testImplementation "org.springframework:spring-test" - testImplementation 'com.squareup.okhttp3:mockwebserver' testRuntimeOnly 'org.hsqldb:hsqldb' } diff --git a/web/src/main/java/org/springframework/security/web/FilterChainProxy.java b/web/src/main/java/org/springframework/security/web/FilterChainProxy.java index 90a26baaaca..b57a1b5e68a 100644 --- a/web/src/main/java/org/springframework/security/web/FilterChainProxy.java +++ b/web/src/main/java/org/springframework/security/web/FilterChainProxy.java @@ -411,7 +411,7 @@ default FilterChain decorate(FilterChain original) { /** * Provide a new {@link FilterChain} that accounts for the provided filters as - * well as the original filter chain. + * well as teh original filter chain. * @param original the original {@link FilterChain} * @param filters the security filters * @return a security-enabled {@link FilterChain} that includes the provided diff --git a/web/src/main/java/org/springframework/security/web/access/IpAddressAuthorizationManager.java b/web/src/main/java/org/springframework/security/web/access/IpAddressAuthorizationManager.java deleted file mode 100644 index 35e9cfdbd23..00000000000 --- a/web/src/main/java/org/springframework/security/web/access/IpAddressAuthorizationManager.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-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.web.access; - -import java.util.function.Supplier; - -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.access.intercept.RequestAuthorizationContext; -import org.springframework.security.web.util.matcher.IpAddressMatcher; -import org.springframework.util.Assert; - -/** - * A {@link AuthorizationManager}, that determines if the current request contains the - * specified address or range of addresses - * - * @author brunodmartins - * @since 6.3 - */ -public final class IpAddressAuthorizationManager implements AuthorizationManager { - - private final IpAddressMatcher ipAddressMatcher; - - IpAddressAuthorizationManager(String ipAddress) { - this.ipAddressMatcher = new IpAddressMatcher(ipAddress); - } - - /** - * Creates an instance of {@link IpAddressAuthorizationManager} with the provided IP - * address. - * @param ipAddress the address or range of addresses from which the request must - * @return the new instance - */ - public static IpAddressAuthorizationManager hasIpAddress(String ipAddress) { - Assert.notNull(ipAddress, "ipAddress cannot be null"); - return new IpAddressAuthorizationManager(ipAddress); - } - - @Override - public AuthorizationDecision check(Supplier authentication, - RequestAuthorizationContext requestAuthorizationContext) { - return new AuthorizationDecision( - this.ipAddressMatcher.matcher(requestAuthorizationContext.getRequest()).isMatch()); - } - -} diff --git a/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverter.java b/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverter.java deleted file mode 100644 index f10e3cffcb3..00000000000 --- a/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverter.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.authentication; - -import java.util.ArrayList; -import java.util.List; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * A {@link AuthenticationConverter}, that iterates over multiple - * {@link AuthenticationConverter}. The first non-null {@link Authentication} will be used - * as a result. - * - * @author Max Batischev - * @since 6.3 - */ -public final class DelegatingAuthenticationConverter implements AuthenticationConverter { - - private final List delegates; - - public DelegatingAuthenticationConverter(List delegates) { - Assert.notEmpty(delegates, "delegates cannot be null"); - this.delegates = new ArrayList<>(delegates); - } - - public DelegatingAuthenticationConverter(AuthenticationConverter... delegates) { - Assert.notEmpty(delegates, "delegates cannot be null"); - this.delegates = List.of(delegates); - } - - @Override - public Authentication convert(HttpServletRequest request) { - for (AuthenticationConverter delegate : this.delegates) { - Authentication authentication = delegate.convert(request); - if (authentication != null) { - return authentication; - } - } - return null; - } - -} diff --git a/web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordChecker.java b/web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordChecker.java deleted file mode 100644 index 37c1b26b842..00000000000 --- a/web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordChecker.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.authentication.password; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Collections; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.lang.NonNull; -import org.springframework.security.authentication.password.CompromisedPasswordCheckResult; -import org.springframework.security.authentication.password.CompromisedPasswordChecker; -import org.springframework.security.crypto.codec.Hex; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestClient; -import org.springframework.web.client.RestClientException; - -/** - * Checks if the provided password was leaked by relying on - * Have I Been Pwned REST - * API. This implementation uses the Search by Range in order to protect the value of - * the source password being searched for. - * - * @author Marcus da Coregio - * @since 6.3 - */ -public final class HaveIBeenPwnedRestApiPasswordChecker implements CompromisedPasswordChecker { - - private static final String API_URL = "https://api.pwnedpasswords.com/range/"; - - private static final int PREFIX_LENGTH = 5; - - private final Log logger = LogFactory.getLog(getClass()); - - private final MessageDigest sha1Digest; - - private RestClient restClient = RestClient.builder().baseUrl(API_URL).build(); - - public HaveIBeenPwnedRestApiPasswordChecker() { - this.sha1Digest = getSha1Digest(); - } - - @Override - @NonNull - public CompromisedPasswordCheckResult check(String password) { - byte[] hash = this.sha1Digest.digest(password.getBytes(StandardCharsets.UTF_8)); - String encoded = new String(Hex.encode(hash)).toUpperCase(); - String prefix = encoded.substring(0, PREFIX_LENGTH); - String suffix = encoded.substring(PREFIX_LENGTH); - - List passwords = getLeakedPasswordsForPrefix(prefix); - boolean isLeaked = findLeakedPassword(passwords, suffix); - return new CompromisedPasswordCheckResult(isLeaked); - } - - /** - * Sets the {@link RestClient} to use when making requests to Have I Been Pwned REST - * API. By default, a {@link RestClient} with a base URL of {@link #API_URL} is used. - * @param restClient the {@link RestClient} to use - */ - public void setRestClient(RestClient restClient) { - Assert.notNull(restClient, "restClient cannot be null"); - this.restClient = restClient; - } - - private boolean findLeakedPassword(List passwords, String suffix) { - for (String pw : passwords) { - if (pw.startsWith(suffix)) { - return true; - } - } - return false; - } - - private List getLeakedPasswordsForPrefix(String prefix) { - try { - String response = this.restClient.get().uri(prefix).retrieve().body(String.class); - if (!StringUtils.hasText(response)) { - return Collections.emptyList(); - } - return response.lines().toList(); - } - catch (RestClientException ex) { - this.logger.error("Request for leaked passwords failed", ex); - return Collections.emptyList(); - } - } - - private static MessageDigest getSha1Digest() { - try { - return MessageDigest.getInstance("SHA-1"); - } - catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex.getMessage()); - } - } - -} diff --git a/web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiReactivePasswordChecker.java b/web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiReactivePasswordChecker.java deleted file mode 100644 index 3ef10f46d08..00000000000 --- a/web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiReactivePasswordChecker.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.authentication.password; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -import org.springframework.security.authentication.password.CompromisedPasswordCheckResult; -import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker; -import org.springframework.security.crypto.codec.Hex; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClientResponseException; - -/** - * Checks if the provided password was leaked by relying on - * Have I Been Pwned REST - * API. This implementation uses the Search by Range in order to protect the value of - * the source password being searched for. - * - * @author Marcus da Coregio - * @since 6.3 - */ -public class HaveIBeenPwnedRestApiReactivePasswordChecker implements ReactiveCompromisedPasswordChecker { - - private static final String API_URL = "https://api.pwnedpasswords.com/range/"; - - private static final int PREFIX_LENGTH = 5; - - private final Log logger = LogFactory.getLog(getClass()); - - private WebClient webClient = WebClient.builder().baseUrl(API_URL).build(); - - private final MessageDigest sha1Digest; - - public HaveIBeenPwnedRestApiReactivePasswordChecker() { - this.sha1Digest = getSha1Digest(); - } - - @Override - public Mono check(String password) { - return getHash(password).map((hash) -> new String(Hex.encode(hash))) - .flatMap(this::findLeakedPassword) - .map(CompromisedPasswordCheckResult::new); - } - - private Mono findLeakedPassword(String encodedPassword) { - String prefix = encodedPassword.substring(0, PREFIX_LENGTH).toUpperCase(); - String suffix = encodedPassword.substring(PREFIX_LENGTH).toUpperCase(); - return getLeakedPasswordsForPrefix(prefix).any((leakedPw) -> leakedPw.startsWith(suffix)); - } - - private Flux getLeakedPasswordsForPrefix(String prefix) { - return this.webClient.get().uri(prefix).retrieve().bodyToMono(String.class).flatMapMany((body) -> { - if (StringUtils.hasText(body)) { - return Flux.fromStream(body.lines()); - } - return Flux.empty(); - }) - .doOnError((ex) -> this.logger.error("Request for leaked passwords failed", ex)) - .onErrorResume(WebClientResponseException.class, (ex) -> Flux.empty()); - } - - /** - * Sets the {@link WebClient} to use when making requests to Have I Been Pwned REST - * API. By default, a {@link WebClient} with a base URL of {@link #API_URL} is used. - * @param webClient the {@link WebClient} to use - */ - public void setWebClient(WebClient webClient) { - Assert.notNull(webClient, "webClient cannot be null"); - this.webClient = webClient; - } - - private Mono getHash(String password) { - return Mono.fromSupplier(() -> this.sha1Digest.digest(password.getBytes(StandardCharsets.UTF_8))) - .subscribeOn(Schedulers.boundedElastic()) - .publishOn(Schedulers.parallel()); - } - - private static MessageDigest getSha1Digest() { - try { - return MessageDigest.getInstance("SHA-1"); - } - catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex.getMessage()); - } - } - -} diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/SessionAuthenticationException.java b/web/src/main/java/org/springframework/security/web/authentication/session/SessionAuthenticationException.java index db1650b3a9e..b4159f8e58b 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/session/SessionAuthenticationException.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/SessionAuthenticationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,15 +19,12 @@ import org.springframework.security.core.AuthenticationException; /** - * Thrown by an {@link SessionAuthenticationStrategy} or - * {@link ServerSessionAuthenticationStrategy} to indicate that an authentication object - * is not valid for the current session, typically because the same user has exceeded the - * number of sessions they are allowed to have concurrently. + * Thrown by an SessionAuthenticationStrategy to indicate that an authentication + * object is not valid for the current session, typically because the same user has + * exceeded the number of sessions they are allowed to have concurrently. * * @author Luke Taylor * @since 3.0 - * @see SessionAuthenticationStrategy - * @see ServerSessionAuthenticationStrategy */ public class SessionAuthenticationException extends AuthenticationException { diff --git a/web/src/main/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixIn.java b/web/src/main/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixIn.java deleted file mode 100644 index 4fff7ffdc41..00000000000 --- a/web/src/main/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixIn.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.jackson2; - -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.web.authentication.switchuser.SwitchUserGrantedAuthority; - -/** - * Jackson mixin class to serialize/deserialize {@link SwitchUserGrantedAuthority}. - * - * @author Markus Heiden - * @since 6.3 - * @see WebServletJackson2Module - * @see org.springframework.security.jackson2.SecurityJackson2Modules - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, - isGetterVisibility = JsonAutoDetect.Visibility.NONE) -@JsonIgnoreProperties(ignoreUnknown = true) -abstract class SwitchUserGrantedAuthorityMixIn { - - @JsonCreator - SwitchUserGrantedAuthorityMixIn(@JsonProperty("role") String role, @JsonProperty("source") Authentication source) { - } - -} diff --git a/web/src/main/java/org/springframework/security/web/jackson2/WebServletJackson2Module.java b/web/src/main/java/org/springframework/security/web/jackson2/WebServletJackson2Module.java index 70b098e4fed..3a95a3c082a 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/WebServletJackson2Module.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/WebServletJackson2Module.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,17 +22,16 @@ import org.springframework.security.jackson2.SecurityJackson2Modules; import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority; import org.springframework.security.web.savedrequest.DefaultSavedRequest; import org.springframework.security.web.savedrequest.SavedCookie; /** - * Jackson module for spring-security-web related to servlet. This module registers - * {@link CookieMixin}, {@link SavedCookieMixin}, {@link DefaultSavedRequestMixin}, - * {@link WebAuthenticationDetailsMixin}, and {@link SwitchUserGrantedAuthorityMixIn}. If - * no default typing is enabled by default then it will be enabled, because typing info is - * needed to properly serialize/deserialize objects. In order to use this module just add - * this module into your ObjectMapper configuration. + * Jackson module for spring-security-web related to servlet. This module register + * {@link CookieMixin}, {@link SavedCookieMixin}, {@link DefaultSavedRequestMixin} and + * {@link WebAuthenticationDetailsMixin}. If no default typing enabled by default then + * it'll enable it because typing info is needed to properly serialize/deserialize + * objects. In order to use this module just add this module into your ObjectMapper + * configuration. * *
      *     ObjectMapper mapper = new ObjectMapper();
    @@ -57,7 +56,6 @@ public void setupModule(SetupContext context) {
     		context.setMixInAnnotations(SavedCookie.class, SavedCookieMixin.class);
     		context.setMixInAnnotations(DefaultSavedRequest.class, DefaultSavedRequestMixin.class);
     		context.setMixInAnnotations(WebAuthenticationDetails.class, WebAuthenticationDetailsMixin.class);
    -		context.setMixInAnnotations(SwitchUserGrantedAuthority.class, SwitchUserGrantedAuthorityMixIn.class);
     	}
     
     }
    diff --git a/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java b/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java
    index d1a6ba12a7d..05ef53a59ca 100644
    --- a/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java
    +++ b/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java
    @@ -85,8 +85,7 @@ public final class CurrentSecurityContextArgumentResolver implements HandlerMeth
     
     	@Override
     	public boolean supportsParameter(MethodParameter parameter) {
    -		return SecurityContext.class.isAssignableFrom(parameter.getParameterType())
    -				|| findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
    +		return findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
     	}
     
     	@Override
    @@ -96,12 +95,26 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
     		if (securityContext == null) {
     			return null;
     		}
    +		Object securityContextResult = securityContext;
     		CurrentSecurityContext annotation = findMethodAnnotation(CurrentSecurityContext.class, parameter);
    -		if (annotation != null) {
    -			return resolveSecurityContextFromAnnotation(parameter, annotation, securityContext);
    +		String expressionToParse = annotation.expression();
    +		if (StringUtils.hasLength(expressionToParse)) {
    +			StandardEvaluationContext context = new StandardEvaluationContext();
    +			context.setRootObject(securityContext);
    +			context.setVariable("this", securityContext);
    +			context.setBeanResolver(this.beanResolver);
    +			Expression expression = this.parser.parseExpression(expressionToParse);
    +			securityContextResult = expression.getValue(context);
     		}
    -
    -		return securityContext;
    +		if (securityContextResult != null
    +				&& !parameter.getParameterType().isAssignableFrom(securityContextResult.getClass())) {
    +			if (annotation.errorOnInvalidType()) {
    +				throw new ClassCastException(
    +						securityContextResult + " is not assignable to " + parameter.getParameterType());
    +			}
    +			return null;
    +		}
    +		return securityContextResult;
     	}
     
     	/**
    @@ -124,29 +137,6 @@ public void setBeanResolver(BeanResolver beanResolver) {
     		this.beanResolver = beanResolver;
     	}
     
    -	private Object resolveSecurityContextFromAnnotation(MethodParameter parameter, CurrentSecurityContext annotation,
    -			SecurityContext securityContext) {
    -		Object securityContextResult = securityContext;
    -		String expressionToParse = annotation.expression();
    -		if (StringUtils.hasLength(expressionToParse)) {
    -			StandardEvaluationContext context = new StandardEvaluationContext();
    -			context.setRootObject(securityContext);
    -			context.setVariable("this", securityContext);
    -			context.setBeanResolver(this.beanResolver);
    -			Expression expression = this.parser.parseExpression(expressionToParse);
    -			securityContextResult = expression.getValue(context);
    -		}
    -		if (securityContextResult != null
    -				&& !parameter.getParameterType().isAssignableFrom(securityContextResult.getClass())) {
    -			if (annotation.errorOnInvalidType()) {
    -				throw new ClassCastException(
    -						securityContextResult + " is not assignable to " + parameter.getParameterType());
    -			}
    -			return null;
    -		}
    -		return securityContextResult;
    -	}
    -
     	/**
     	 * Obtain the specified {@link Annotation} on the specified {@link MethodParameter}.
     	 * @param annotationClass the class of the {@link Annotation} to find on the
    diff --git a/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolver.java b/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolver.java
    index fd51d8ac533..a02a9c30b9f 100644
    --- a/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolver.java
    +++ b/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolver.java
    @@ -67,21 +67,7 @@ public void setBeanResolver(BeanResolver beanResolver) {
     
     	@Override
     	public boolean supportsParameter(MethodParameter parameter) {
    -		return isMonoSecurityContext(parameter)
    -				|| findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
    -	}
    -
    -	private boolean isMonoSecurityContext(MethodParameter parameter) {
    -		boolean isParameterPublisher = Publisher.class.isAssignableFrom(parameter.getParameterType());
    -		if (isParameterPublisher) {
    -			ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
    -			Class genericType = resolvableType.resolveGeneric(0);
    -			if (genericType == null) {
    -				return false;
    -			}
    -			return SecurityContext.class.isAssignableFrom(genericType);
    -		}
    -		return false;
    +		return findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
     	}
     
     	@Override
    @@ -109,14 +95,6 @@ public Mono resolveArgument(MethodParameter parameter, BindingContext bi
     	 */
     	private Object resolveSecurityContext(MethodParameter parameter, SecurityContext securityContext) {
     		CurrentSecurityContext annotation = findMethodAnnotation(CurrentSecurityContext.class, parameter);
    -		if (annotation != null) {
    -			return resolveSecurityContextFromAnnotation(annotation, parameter, securityContext);
    -		}
    -		return securityContext;
    -	}
    -
    -	private Object resolveSecurityContextFromAnnotation(CurrentSecurityContext annotation, MethodParameter parameter,
    -			Object securityContext) {
     		Object securityContextResult = securityContext;
     		String expressionToParse = annotation.expression();
     		if (StringUtils.hasLength(expressionToParse)) {
    diff --git a/web/src/main/java/org/springframework/security/web/server/WebFilterChainProxy.java b/web/src/main/java/org/springframework/security/web/server/WebFilterChainProxy.java
    index d1e9d8fe575..7327c3436dc 100644
    --- a/web/src/main/java/org/springframework/security/web/server/WebFilterChainProxy.java
    +++ b/web/src/main/java/org/springframework/security/web/server/WebFilterChainProxy.java
    @@ -98,7 +98,7 @@ default WebFilterChain decorate(WebFilterChain original) {
     
     		/**
     		 * Provide a new {@link FilterChain} that accounts for the provided filters as
    -		 * well as the original filter chain.
    +		 * well as teh original filter chain.
     		 * @param original the original {@link FilterChain}
     		 * @param filters the security filters
     		 * @return a security-enabled {@link FilterChain} that includes the provided
    diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.java
    deleted file mode 100644
    index cc28044da7c..00000000000
    --- a/web/src/main/java/org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.java
    +++ /dev/null
    @@ -1,103 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.server.authentication;
    -
    -import java.util.List;
    -
    -import reactor.core.publisher.Mono;
    -import reactor.util.function.Tuples;
    -
    -import org.springframework.security.core.Authentication;
    -import org.springframework.security.core.session.ReactiveSessionInformation;
    -import org.springframework.security.core.session.ReactiveSessionRegistry;
    -import org.springframework.security.web.server.WebFilterExchange;
    -import org.springframework.util.Assert;
    -import org.springframework.web.server.WebSession;
    -
    -/**
    - * Controls the number of sessions a user can have concurrently authenticated in an
    - * application. It also allows for customizing behaviour when an authentication attempt is
    - * made while the user already has the maximum number of sessions open. By default, it
    - * allows a maximum of 1 session per user, if the maximum is exceeded, the user's least
    - * recently used session(s) will be expired.
    - *
    - * @author Marcus da Coregio
    - * @since 6.3
    - * @see ServerMaximumSessionsExceededHandler
    - * @see RegisterSessionServerAuthenticationSuccessHandler
    - */
    -public final class ConcurrentSessionControlServerAuthenticationSuccessHandler
    -		implements ServerAuthenticationSuccessHandler {
    -
    -	private final ReactiveSessionRegistry sessionRegistry;
    -
    -	private final ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler;
    -
    -	private SessionLimit sessionLimit = SessionLimit.of(1);
    -
    -	public ConcurrentSessionControlServerAuthenticationSuccessHandler(ReactiveSessionRegistry sessionRegistry,
    -			ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler) {
    -		Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
    -		Assert.notNull(maximumSessionsExceededHandler, "maximumSessionsExceededHandler cannot be null");
    -		this.sessionRegistry = sessionRegistry;
    -		this.maximumSessionsExceededHandler = maximumSessionsExceededHandler;
    -	}
    -
    -	@Override
    -	public Mono onAuthenticationSuccess(WebFilterExchange exchange, Authentication authentication) {
    -		return this.sessionLimit.apply(authentication)
    -			.flatMap((maxSessions) -> handleConcurrency(exchange, authentication, maxSessions));
    -	}
    -
    -	private Mono handleConcurrency(WebFilterExchange exchange, Authentication authentication,
    -			Integer maximumSessions) {
    -		return this.sessionRegistry.getAllSessions(authentication.getPrincipal())
    -			.collectList()
    -			.flatMap((registeredSessions) -> exchange.getExchange()
    -				.getSession()
    -				.map((currentSession) -> Tuples.of(currentSession, registeredSessions)))
    -			.flatMap((sessionTuple) -> {
    -				WebSession currentSession = sessionTuple.getT1();
    -				List registeredSessions = sessionTuple.getT2();
    -				int registeredSessionsCount = registeredSessions.size();
    -				if (registeredSessionsCount < maximumSessions) {
    -					return Mono.empty();
    -				}
    -				if (registeredSessionsCount == maximumSessions) {
    -					for (ReactiveSessionInformation registeredSession : registeredSessions) {
    -						if (registeredSession.getSessionId().equals(currentSession.getId())) {
    -							return Mono.empty();
    -						}
    -					}
    -				}
    -				return this.maximumSessionsExceededHandler.handle(new MaximumSessionsContext(authentication,
    -						registeredSessions, maximumSessions, currentSession));
    -			});
    -	}
    -
    -	/**
    -	 * Sets the strategy used to resolve the maximum number of sessions that are allowed
    -	 * for a specific {@link Authentication}. By default, it returns {@code 1} for any
    -	 * authentication.
    -	 * @param sessionLimit the {@link SessionLimit} to use
    -	 */
    -	public void setSessionLimit(SessionLimit sessionLimit) {
    -		Assert.notNull(sessionLimit, "sessionLimit cannot be null");
    -		this.sessionLimit = sessionLimit;
    -	}
    -
    -}
    diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationConverter.java b/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationConverter.java
    deleted file mode 100644
    index d2ff68c9816..00000000000
    --- a/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationConverter.java
    +++ /dev/null
    @@ -1,72 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.server.authentication;
    -
    -import java.util.List;
    -import java.util.function.Function;
    -
    -import org.apache.commons.logging.Log;
    -import org.apache.commons.logging.LogFactory;
    -import reactor.core.publisher.Flux;
    -import reactor.core.publisher.Mono;
    -
    -import org.springframework.security.core.Authentication;
    -import org.springframework.util.Assert;
    -import org.springframework.web.server.ServerWebExchange;
    -
    -/**
    - * A {@link ServerAuthenticationConverter} that delegates to other
    - * {@link ServerAuthenticationConverter} instances.
    - *
    - * @author DingHao
    - * @since 6.3
    - */
    -public final class DelegatingServerAuthenticationConverter implements ServerAuthenticationConverter {
    -
    -	private final List delegates;
    -
    -	private boolean continueOnError = false;
    -
    -	private final Log logger = LogFactory.getLog(getClass());
    -
    -	public DelegatingServerAuthenticationConverter(ServerAuthenticationConverter... converters) {
    -		this(List.of(converters));
    -	}
    -
    -	public DelegatingServerAuthenticationConverter(List converters) {
    -		Assert.notEmpty(converters, "converters cannot be null");
    -		this.delegates = converters;
    -	}
    -
    -	@Override
    -	public Mono convert(ServerWebExchange exchange) {
    -		Flux result = Flux.fromIterable(this.delegates);
    -		Function> logging = (
    -				converter) -> converter.convert(exchange).doOnError(this.logger::debug);
    -		return ((this.continueOnError) ? result.concatMapDelayError(logging) : result.concatMap(logging)).next();
    -	}
    -
    -	/**
    -	 * Continue iterating when a delegate errors, defaults to {@code false}
    -	 * @param continueOnError whether to continue when a delegate errors
    -	 * @since 6.3
    -	 */
    -	public void setContinueOnError(boolean continueOnError) {
    -		this.continueOnError = continueOnError;
    -	}
    -
    -}
    diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandler.java
    index d74ffed43c7..851e80620a5 100644
    --- a/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandler.java
    +++ b/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandler.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2023 the original author or authors.
    + * Copyright 2002-2018 the original author or authors.
      *
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
    @@ -42,16 +42,6 @@ public DelegatingServerAuthenticationSuccessHandler(ServerAuthenticationSuccessH
     		this.delegates = Arrays.asList(delegates);
     	}
     
    -	/**
    -	 * Creates a new instance with the provided list of delegates
    -	 * @param delegates the {@link List} of {@link ServerAuthenticationSuccessHandler}
    -	 * @since 6.3
    -	 */
    -	public DelegatingServerAuthenticationSuccessHandler(List delegates) {
    -		Assert.notEmpty(delegates, "delegates cannot be null or empty");
    -		this.delegates = delegates;
    -	}
    -
     	@Override
     	public Mono onAuthenticationSuccess(WebFilterExchange exchange, Authentication authentication) {
     		return Flux.fromIterable(this.delegates)
    diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/InvalidateLeastUsedServerMaximumSessionsExceededHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/InvalidateLeastUsedServerMaximumSessionsExceededHandler.java
    deleted file mode 100644
    index 28446df993e..00000000000
    --- a/web/src/main/java/org/springframework/security/web/server/authentication/InvalidateLeastUsedServerMaximumSessionsExceededHandler.java
    +++ /dev/null
    @@ -1,62 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.server.authentication;
    -
    -import java.util.ArrayList;
    -import java.util.Comparator;
    -import java.util.List;
    -
    -import reactor.core.publisher.Flux;
    -import reactor.core.publisher.Mono;
    -
    -import org.springframework.security.core.session.ReactiveSessionInformation;
    -import org.springframework.web.server.session.WebSessionStore;
    -
    -/**
    - * Implementation of {@link ServerMaximumSessionsExceededHandler} that invalidates the
    - * least recently used {@link ReactiveSessionInformation} and removes the related sessions
    - * from the {@link WebSessionStore}. It only invalidates the amount of sessions that
    - * exceed the maximum allowed. For example, if the maximum was exceeded by 1, only the
    - * least recently used session will be invalidated.
    - *
    - * @author Marcus da Coregio
    - * @since 6.3
    - */
    -public final class InvalidateLeastUsedServerMaximumSessionsExceededHandler
    -		implements ServerMaximumSessionsExceededHandler {
    -
    -	private final WebSessionStore webSessionStore;
    -
    -	public InvalidateLeastUsedServerMaximumSessionsExceededHandler(WebSessionStore webSessionStore) {
    -		this.webSessionStore = webSessionStore;
    -	}
    -
    -	@Override
    -	public Mono handle(MaximumSessionsContext context) {
    -		List sessions = new ArrayList<>(context.getSessions());
    -		sessions.sort(Comparator.comparing(ReactiveSessionInformation::getLastAccessTime));
    -		int maximumSessionsExceededBy = sessions.size() - context.getMaximumSessionsAllowed() + 1;
    -		List leastRecentlyUsedSessionsToInvalidate = sessions.subList(0,
    -				maximumSessionsExceededBy);
    -
    -		return Flux.fromIterable(leastRecentlyUsedSessionsToInvalidate)
    -			.flatMap((toInvalidate) -> toInvalidate.invalidate().thenReturn(toInvalidate))
    -			.flatMap((toInvalidate) -> this.webSessionStore.removeSession(toInvalidate.getSessionId()))
    -			.then();
    -	}
    -
    -}
    diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/MaximumSessionsContext.java b/web/src/main/java/org/springframework/security/web/server/authentication/MaximumSessionsContext.java
    deleted file mode 100644
    index 9ba11bc17d1..00000000000
    --- a/web/src/main/java/org/springframework/security/web/server/authentication/MaximumSessionsContext.java
    +++ /dev/null
    @@ -1,59 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.server.authentication;
    -
    -import java.util.List;
    -
    -import org.springframework.security.core.Authentication;
    -import org.springframework.security.core.session.ReactiveSessionInformation;
    -import org.springframework.web.server.WebSession;
    -
    -public final class MaximumSessionsContext {
    -
    -	private final Authentication authentication;
    -
    -	private final List sessions;
    -
    -	private final int maximumSessionsAllowed;
    -
    -	private final WebSession currentSession;
    -
    -	public MaximumSessionsContext(Authentication authentication, List sessions,
    -			int maximumSessionsAllowed, WebSession currentSession) {
    -		this.authentication = authentication;
    -		this.sessions = sessions;
    -		this.maximumSessionsAllowed = maximumSessionsAllowed;
    -		this.currentSession = currentSession;
    -	}
    -
    -	public Authentication getAuthentication() {
    -		return this.authentication;
    -	}
    -
    -	public List getSessions() {
    -		return this.sessions;
    -	}
    -
    -	public int getMaximumSessionsAllowed() {
    -		return this.maximumSessionsAllowed;
    -	}
    -
    -	public WebSession getCurrentSession() {
    -		return this.currentSession;
    -	}
    -
    -}
    diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/PreventLoginServerMaximumSessionsExceededHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/PreventLoginServerMaximumSessionsExceededHandler.java
    deleted file mode 100644
    index 1afb5771e34..00000000000
    --- a/web/src/main/java/org/springframework/security/web/server/authentication/PreventLoginServerMaximumSessionsExceededHandler.java
    +++ /dev/null
    @@ -1,39 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.server.authentication;
    -
    -import reactor.core.publisher.Mono;
    -
    -import org.springframework.security.web.authentication.session.SessionAuthenticationException;
    -
    -/**
    - * Returns a {@link Mono} that terminates with {@link SessionAuthenticationException} when
    - * the maximum number of sessions for a user has been reached.
    - *
    - * @author Marcus da Coregio
    - * @since 6.3
    - */
    -public final class PreventLoginServerMaximumSessionsExceededHandler implements ServerMaximumSessionsExceededHandler {
    -
    -	@Override
    -	public Mono handle(MaximumSessionsContext context) {
    -		return context.getCurrentSession()
    -			.invalidate()
    -			.then(Mono.defer(() -> Mono.error(new SessionAuthenticationException("Maximum sessions exceeded"))));
    -	}
    -
    -}
    diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.java
    deleted file mode 100644
    index bc086e23cba..00000000000
    --- a/web/src/main/java/org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.java
    +++ /dev/null
    @@ -1,52 +0,0 @@
    -/*
    - * Copyright 2002-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.web.server.authentication;
    -
    -import reactor.core.publisher.Mono;
    -
    -import org.springframework.security.core.Authentication;
    -import org.springframework.security.core.session.ReactiveSessionInformation;
    -import org.springframework.security.core.session.ReactiveSessionRegistry;
    -import org.springframework.security.web.server.WebFilterExchange;
    -import org.springframework.util.Assert;
    -
    -/**
    - * An implementation of {@link ServerAuthenticationSuccessHandler} that will register a
    - * {@link ReactiveSessionInformation} with the provided {@link ReactiveSessionRegistry}.
    - *
    - * @author Marcus da Coregio
    - * @since 6.3
    - */
    -public final class RegisterSessionServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
    -
    -	private final ReactiveSessionRegistry sessionRegistry;
    -
    -	public RegisterSessionServerAuthenticationSuccessHandler(ReactiveSessionRegistry sessionRegistry) {
    -		Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
    -		this.sessionRegistry = sessionRegistry;
    -	}
    -
    -	@Override
    -	public Mono onAuthenticationSuccess(WebFilterExchange exchange, Authentication authentication) {
    -		return exchange.getExchange()
    -			.getSession()
    -			.map((session) -> new ReactiveSessionInformation(authentication.getPrincipal(), session.getId(),
    -					session.getLastAccessTime()))
    -			.flatMap(this.sessionRegistry::saveSessionInformation);
    -	}
    -
    -}
    diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/ServerMaximumSessionsExceededHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/ServerMaximumSessionsExceededHandler.java
    deleted file mode 100644
    index 6b90cbcd102..00000000000
    --- a/web/src/main/java/org/springframework/security/web/server/authentication/ServerMaximumSessionsExceededHandler.java
    +++ /dev/null
    @@ -1,38 +0,0 @@
    -/*
    - * Copyright 2002-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.web.server.authentication;
    -
    -import reactor.core.publisher.Mono;
    -
    -/**
    - * Strategy for handling the scenario when the maximum number of sessions for a user has
    - * been reached.
    - *
    - * @author Marcus da Coregio
    - * @since 6.3
    - */
    -public interface ServerMaximumSessionsExceededHandler {
    -
    -	/**
    -	 * Handles the scenario when the maximum number of sessions for a user has been
    -	 * reached.
    -	 * @param context the context with information about the sessions and the user
    -	 * @return an empty {@link Mono} that completes when the handling is done
    -	 */
    -	Mono handle(MaximumSessionsContext context);
    -
    -}
    diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.java b/web/src/main/java/org/springframework/security/web/server/authentication/ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.java
    index 4605b0d1993..686f891fa16 100644
    --- a/web/src/main/java/org/springframework/security/web/server/authentication/ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.java
    +++ b/web/src/main/java/org/springframework/security/web/server/authentication/ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2024 the original author or authors.
    + * Copyright 2002-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.
    @@ -27,6 +27,7 @@
     import org.springframework.security.authentication.ReactiveAuthenticationManager;
     import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
     import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
    +import org.springframework.security.web.authentication.RequestMatcherDelegatingAuthenticationManagerResolver;
     import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
     import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcherEntry;
     import org.springframework.util.Assert;
    @@ -111,8 +112,7 @@ private Mono isMatch(ServerWebExchange exchange,
     	}
     
     	/**
    -	 * A builder for
    -	 * {@link ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver}.
    +	 * A builder for {@link RequestMatcherDelegatingAuthenticationManagerResolver}.
     	 */
     	public static final class Builder {
     
    @@ -128,8 +128,8 @@ private Builder() {
     		 * @param matcher the {@link ServerWebExchangeMatcher} to use
     		 * @param manager the {@link ReactiveAuthenticationManager} to use
     		 * @return the
    -		 * {@link ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.Builder}
    -		 * for further customizations
    +		 * {@link RequestMatcherDelegatingAuthenticationManagerResolver.Builder} for
    +		 * further customizations
     		 */
     		public ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.Builder add(
     				ServerWebExchangeMatcher matcher, ReactiveAuthenticationManager manager) {
    @@ -140,11 +140,9 @@ public ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.Builder
     		}
     
     		/**
    -		 * Creates a
    -		 * {@link ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver}
    +		 * Creates a {@link RequestMatcherDelegatingAuthenticationManagerResolver}
     		 * instance.
    -		 * @return the
    -		 * {@link ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver}
    +		 * @return the {@link RequestMatcherDelegatingAuthenticationManagerResolver}
     		 * instance
     		 */
     		public ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver build() {
    diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/SessionLimit.java b/web/src/main/java/org/springframework/security/web/server/authentication/SessionLimit.java
    deleted file mode 100644
    index ce96937500a..00000000000
    --- a/web/src/main/java/org/springframework/security/web/server/authentication/SessionLimit.java
    +++ /dev/null
    @@ -1,50 +0,0 @@
    -/*
    - * Copyright 2002-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.web.server.authentication;
    -
    -import java.util.function.Function;
    -
    -import reactor.core.publisher.Mono;
    -
    -import org.springframework.security.core.Authentication;
    -
    -/**
    - * Represents the maximum number of sessions allowed. Use {@link #UNLIMITED} to indicate
    - * that there is no limit.
    - *
    - * @author Marcus da Coregio
    - * @since 6.3
    - * @see ConcurrentSessionControlServerAuthenticationSuccessHandler
    - */
    -public interface SessionLimit extends Function> {
    -
    -	/**
    -	 * Represents unlimited sessions. This is just a shortcut to return
    -	 * {@link Mono#empty()} for any user.
    -	 */
    -	SessionLimit UNLIMITED = (authentication) -> Mono.empty();
    -
    -	/**
    -	 * Creates a {@link SessionLimit} that always returns the given value for any user
    -	 * @param maxSessions the maximum number of sessions allowed
    -	 * @return a {@link SessionLimit} instance that returns the given value.
    -	 */
    -	static SessionLimit of(int maxSessions) {
    -		return (authentication) -> Mono.just(maxSessions);
    -	}
    -
    -}
    diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/IpAddressMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/IpAddressMatcher.java
    index 80c344fb275..3be7851094d 100644
    --- a/web/src/main/java/org/springframework/security/web/util/matcher/IpAddressMatcher.java
    +++ b/web/src/main/java/org/springframework/security/web/util/matcher/IpAddressMatcher.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2024 the original author or authors.
    + * Copyright 2002-2019 the original author or authors.
      *
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
    @@ -47,7 +47,6 @@ public final class IpAddressMatcher implements RequestMatcher {
     	 * come.
     	 */
     	public IpAddressMatcher(String ipAddress) {
    -		assertStartsWithHexa(ipAddress);
     		if (ipAddress.indexOf('/') > 0) {
     			String[] addressAndMask = StringUtils.split(ipAddress, "/");
     			ipAddress = addressAndMask[0];
    @@ -57,9 +56,8 @@ public IpAddressMatcher(String ipAddress) {
     			this.nMaskBits = -1;
     		}
     		this.requiredAddress = parseAddress(ipAddress);
    -		String finalIpAddress = ipAddress;
    -		Assert.isTrue(this.requiredAddress.getAddress().length * 8 >= this.nMaskBits, () -> String
    -			.format("IP address %s is too short for bitmask of length %d", finalIpAddress, this.nMaskBits));
    +		Assert.isTrue(this.requiredAddress.getAddress().length * 8 >= this.nMaskBits,
    +				String.format("IP address %s is too short for bitmask of length %d", ipAddress, this.nMaskBits));
     	}
     
     	@Override
    @@ -68,7 +66,6 @@ public boolean matches(HttpServletRequest request) {
     	}
     
     	public boolean matches(String address) {
    -		assertStartsWithHexa(address);
     		InetAddress remoteAddress = parseAddress(address);
     		if (!this.requiredAddress.getClass().equals(remoteAddress.getClass())) {
     			return false;
    @@ -91,13 +88,6 @@ public boolean matches(String address) {
     		return true;
     	}
     
    -	private void assertStartsWithHexa(String ipAddress) {
    -		Assert.isTrue(
    -				ipAddress.charAt(0) == '[' || ipAddress.charAt(0) == ':'
    -						|| Character.digit(ipAddress.charAt(0), 16) != -1,
    -				"ipAddress must start with a [, :, or a hexadecimal digit");
    -	}
    -
     	private InetAddress parseAddress(String address) {
     		try {
     			return InetAddress.getByName(address);
    diff --git a/web/src/test/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverterTests.java b/web/src/test/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverterTests.java
    deleted file mode 100644
    index 6de7ef325ae..00000000000
    --- a/web/src/test/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverterTests.java
    +++ /dev/null
    @@ -1,135 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.authentication;
    -
    -import jakarta.servlet.http.HttpServletRequest;
    -import org.junit.jupiter.api.Test;
    -import org.junit.jupiter.api.extension.ExtendWith;
    -import org.mockito.Mock;
    -import org.mockito.junit.jupiter.MockitoExtension;
    -
    -import org.springframework.http.HttpHeaders;
    -import org.springframework.mock.web.MockHttpServletRequest;
    -import org.springframework.security.authentication.AuthenticationDetailsSource;
    -import org.springframework.security.authentication.BadCredentialsException;
    -import org.springframework.security.authentication.TestingAuthenticationToken;
    -import org.springframework.security.core.Authentication;
    -import org.springframework.security.test.web.CodecTestUtils;
    -import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
    -
    -import static org.assertj.core.api.Assertions.assertThat;
    -import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
    -
    -/**
    - * Tests for {@link DelegatingAuthenticationConverter}.
    - *
    - * @author Max Batischev
    - */
    -@ExtendWith(MockitoExtension.class)
    -public class DelegatingAuthenticationConverterTests {
    -
    -	private static final String X_AUTH_TOKEN_HEADER = "X-Auth-Token";
    -
    -	private static final String TEST_X_AUTH_TOKEN = "test-x-auth-token";
    -
    -	private static final String TEST_CUSTOM_PRINCIPAL = "test_custom_principal";
    -
    -	private static final String TEST_CUSTOM_CREDENTIALS = "test_custom_credentials";
    -
    -	private static final String TEST_BASIC_CREDENTIALS = "username:password";
    -
    -	private static final String INVALID_BASIC_CREDENTIALS = "invalid_credentials";
    -
    -	private DelegatingAuthenticationConverter converter;
    -
    -	@Mock
    -	private AuthenticationDetailsSource authenticationDetailsSource;
    -
    -	@Test
    -	public void requestWhenBasicAuthorizationHeaderIsPresentThenAuthenticates() {
    -		MockHttpServletRequest request = new MockHttpServletRequest();
    -		request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + CodecTestUtils.encodeBase64(TEST_BASIC_CREDENTIALS));
    -		this.converter = new DelegatingAuthenticationConverter(
    -				new BasicAuthenticationConverter(this.authenticationDetailsSource),
    -				new TestNullableAuthenticationConverter());
    -
    -		Authentication authentication = this.converter.convert(request);
    -
    -		assertThat(authentication).isNotNull();
    -		assertThat(authentication.getName()).isEqualTo("username");
    -	}
    -
    -	@Test
    -	public void requestWhenXAuthHeaderIsPresentThenAuthenticates() {
    -		MockHttpServletRequest request = new MockHttpServletRequest();
    -		request.addHeader(X_AUTH_TOKEN_HEADER, TEST_X_AUTH_TOKEN);
    -		this.converter = new DelegatingAuthenticationConverter(new TestAuthenticationConverter(),
    -				new TestNullableAuthenticationConverter());
    -
    -		Authentication authentication = this.converter.convert(request);
    -
    -		assertThat(authentication).isNotNull();
    -		assertThat(authentication.getName()).isEqualTo(TEST_CUSTOM_PRINCIPAL);
    -	}
    -
    -	@Test
    -	public void requestWhenXAuthHeaderIsPresentThenDoesntAuthenticate() {
    -		MockHttpServletRequest request = new MockHttpServletRequest();
    -		request.addHeader(X_AUTH_TOKEN_HEADER, TEST_X_AUTH_TOKEN);
    -		this.converter = new DelegatingAuthenticationConverter(new TestNullableAuthenticationConverter());
    -
    -		Authentication authentication = this.converter.convert(request);
    -
    -		assertThat(authentication).isNull();
    -	}
    -
    -	@Test
    -	public void requestWhenInvalidBasicAuthorizationTokenThenError() {
    -		MockHttpServletRequest request = new MockHttpServletRequest();
    -		request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + CodecTestUtils.encodeBase64(INVALID_BASIC_CREDENTIALS));
    -		this.converter = new DelegatingAuthenticationConverter(
    -				new BasicAuthenticationConverter(this.authenticationDetailsSource),
    -				new TestNullableAuthenticationConverter());
    -
    -		assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.converter.convert(request));
    -	}
    -
    -	private static class TestAuthenticationConverter implements AuthenticationConverter {
    -
    -		@Override
    -		public Authentication convert(HttpServletRequest request) {
    -			String header = request.getHeader(X_AUTH_TOKEN_HEADER);
    -			if (header != null) {
    -				return new TestingAuthenticationToken(TEST_CUSTOM_PRINCIPAL, TEST_CUSTOM_CREDENTIALS);
    -			}
    -			else {
    -				return null;
    -			}
    -		}
    -
    -	}
    -
    -	private static class TestNullableAuthenticationConverter implements AuthenticationConverter {
    -
    -		@Override
    -		public Authentication convert(HttpServletRequest request) {
    -			return null;
    -		}
    -
    -	}
    -
    -}
    diff --git a/web/src/test/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordCheckerTests.java b/web/src/test/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordCheckerTests.java
    deleted file mode 100644
    index c8cdd33eace..00000000000
    --- a/web/src/test/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordCheckerTests.java
    +++ /dev/null
    @@ -1,99 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.authentication.password;
    -
    -import java.io.IOException;
    -
    -import okhttp3.HttpUrl;
    -import okhttp3.mockwebserver.MockResponse;
    -import okhttp3.mockwebserver.MockWebServer;
    -import org.junit.jupiter.api.AfterEach;
    -import org.junit.jupiter.api.BeforeEach;
    -import org.junit.jupiter.api.Test;
    -
    -import org.springframework.security.authentication.password.CompromisedPasswordCheckResult;
    -import org.springframework.web.client.RestClient;
    -
    -import static org.assertj.core.api.Assertions.assertThat;
    -import static org.assertj.core.api.Assertions.assertThatNoException;
    -
    -class HaveIBeenPwnedRestApiPasswordCheckerTests {
    -
    -	private final String pwnedPasswords = """
    -			2CDE4CDCFA5AD7D223BD1800338FBEAA04E:1
    -			2CF90F92EE1941547BB13DFC7D0E0AFE504:1
    -			2D10A6654B6D75908AE572559542245CBFA:6
    -			2D4FCF535FE92B8B950424E16E65EFBFED3:1
    -			2D6980B9098804E7A83DC5831BFBAF3927F:1
    -			2D8D1B3FAACCA6A3C6A91617B2FA32E2F57:1
    -			2DC183F740EE76F27B78EB39C8AD972A757:300185
    -			2DE4C0087846D223DBBCCF071614590F300:3
    -			2DEA2B1D02714099E4B7A874B4364D518F6:1
    -			2E750AE8C4756A20CE040BF3DDF094FA7EC:1
    -			2E90B7B3C5C1181D16C48E273D9AC7F3C16:5
    -			2E991A9162F24F01826D8AF73CA20F2B430:1
    -			2EAE5EA981BFAF29A8869A40BDDADF3879B:2
    -			2F1AC09E3846595E436BBDDDD2189358AF9:1
    -			""";
    -
    -	private final MockWebServer server = new MockWebServer();
    -
    -	private final HaveIBeenPwnedRestApiPasswordChecker passwordChecker = new HaveIBeenPwnedRestApiPasswordChecker();
    -
    -	@BeforeEach
    -	void setup() throws IOException {
    -		this.server.start();
    -		HttpUrl url = this.server.url("/range/");
    -		this.passwordChecker.setRestClient(RestClient.builder().baseUrl(url.toString()).build());
    -	}
    -
    -	@AfterEach
    -	void tearDown() throws IOException {
    -		this.server.shutdown();
    -	}
    -
    -	@Test
    -	void checkWhenPasswordIsLeakedThenIsCompromised() throws InterruptedException {
    -		this.server.enqueue(new MockResponse().setBody(this.pwnedPasswords).setResponseCode(200));
    -		CompromisedPasswordCheckResult check = this.passwordChecker.check("P@ssw0rd");
    -		assertThat(check.isCompromised()).isTrue();
    -		assertThat(this.server.takeRequest().getPath()).isEqualTo("/range/21BD1");
    -	}
    -
    -	@Test
    -	void checkWhenPasswordNotLeakedThenNotCompromised() {
    -		this.server.enqueue(new MockResponse().setBody(this.pwnedPasswords).setResponseCode(200));
    -		CompromisedPasswordCheckResult check = this.passwordChecker.check("My1nCr3d!bL3P@SS0W0RD");
    -		assertThat(check.isCompromised()).isFalse();
    -	}
    -
    -	@Test
    -	void checkWhenNoPasswordsReturnedFromApiCallThenNotCompromised() {
    -		this.server.enqueue(new MockResponse().setResponseCode(200));
    -		CompromisedPasswordCheckResult check = this.passwordChecker.check("123456");
    -		assertThat(check.isCompromised()).isFalse();
    -	}
    -
    -	@Test
    -	void checkWhenResponseStatusNot200ThenNotCompromised() {
    -		this.server.enqueue(new MockResponse().setResponseCode(503));
    -		assertThatNoException().isThrownBy(() -> this.passwordChecker.check("123456"));
    -		this.server.enqueue(new MockResponse().setResponseCode(404));
    -		assertThatNoException().isThrownBy(() -> this.passwordChecker.check("123456"));
    -	}
    -
    -}
    diff --git a/web/src/test/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiReactivePasswordCheckerTests.java b/web/src/test/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiReactivePasswordCheckerTests.java
    deleted file mode 100644
    index 7d5550e6d03..00000000000
    --- a/web/src/test/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiReactivePasswordCheckerTests.java
    +++ /dev/null
    @@ -1,105 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.authentication.password;
    -
    -import java.io.IOException;
    -
    -import okhttp3.HttpUrl;
    -import okhttp3.mockwebserver.MockResponse;
    -import okhttp3.mockwebserver.MockWebServer;
    -import org.junit.jupiter.api.AfterEach;
    -import org.junit.jupiter.api.BeforeEach;
    -import org.junit.jupiter.api.Test;
    -import reactor.test.StepVerifier;
    -
    -import org.springframework.web.reactive.function.client.WebClient;
    -
    -import static org.assertj.core.api.Assertions.assertThat;
    -
    -class HaveIBeenPwnedRestApiReactivePasswordCheckerTests {
    -
    -	private final String pwnedPasswords = """
    -			2CDE4CDCFA5AD7D223BD1800338FBEAA04E:1
    -			2CF90F92EE1941547BB13DFC7D0E0AFE504:1
    -			2D10A6654B6D75908AE572559542245CBFA:6
    -			2D4FCF535FE92B8B950424E16E65EFBFED3:1
    -			2D6980B9098804E7A83DC5831BFBAF3927F:1
    -			2D8D1B3FAACCA6A3C6A91617B2FA32E2F57:1
    -			2DC183F740EE76F27B78EB39C8AD972A757:300185
    -			2DE4C0087846D223DBBCCF071614590F300:3
    -			2DEA2B1D02714099E4B7A874B4364D518F6:1
    -			2E750AE8C4756A20CE040BF3DDF094FA7EC:1
    -			2E90B7B3C5C1181D16C48E273D9AC7F3C16:5
    -			2E991A9162F24F01826D8AF73CA20F2B430:1
    -			2EAE5EA981BFAF29A8869A40BDDADF3879B:2
    -			2F1AC09E3846595E436BBDDDD2189358AF9:1
    -			""";
    -
    -	private final MockWebServer server = new MockWebServer();
    -
    -	private final HaveIBeenPwnedRestApiReactivePasswordChecker passwordChecker = new HaveIBeenPwnedRestApiReactivePasswordChecker();
    -
    -	@BeforeEach
    -	void setup() throws IOException {
    -		this.server.start();
    -		HttpUrl url = this.server.url("/range/");
    -		this.passwordChecker.setWebClient(WebClient.builder().baseUrl(url.toString()).build());
    -	}
    -
    -	@AfterEach
    -	void tearDown() throws IOException {
    -		this.server.shutdown();
    -	}
    -
    -	@Test
    -	void checkWhenPasswordIsLeakedThenIsCompromised() throws InterruptedException {
    -		this.server.enqueue(new MockResponse().setBody(this.pwnedPasswords).setResponseCode(200));
    -		StepVerifier.create(this.passwordChecker.check("P@ssw0rd"))
    -			.assertNext((check) -> assertThat(check.isCompromised()).isTrue())
    -			.verifyComplete();
    -		assertThat(this.server.takeRequest().getPath()).isEqualTo("/range/21BD1");
    -	}
    -
    -	@Test
    -	void checkWhenPasswordNotLeakedThenNotCompromised() {
    -		this.server.enqueue(new MockResponse().setBody(this.pwnedPasswords).setResponseCode(200));
    -		StepVerifier.create(this.passwordChecker.check("My1nCr3d!bL3P@SS0W0RD"))
    -			.assertNext((check) -> assertThat(check.isCompromised()).isFalse())
    -			.verifyComplete();
    -	}
    -
    -	@Test
    -	void checkWhenNoPasswordsReturnedFromApiCallThenNotCompromised() {
    -		this.server.enqueue(new MockResponse().setResponseCode(200));
    -		StepVerifier.create(this.passwordChecker.check("P@ssw0rd"))
    -			.assertNext((check) -> assertThat(check.isCompromised()).isFalse())
    -			.verifyComplete();
    -	}
    -
    -	@Test
    -	void checkWhenResponseStatusNot200ThenNotCompromised() {
    -		this.server.enqueue(new MockResponse().setResponseCode(503));
    -		StepVerifier.create(this.passwordChecker.check("123456"))
    -			.assertNext((check) -> assertThat(check.isCompromised()).isFalse())
    -			.verifyComplete();
    -		this.server.enqueue(new MockResponse().setResponseCode(404));
    -		StepVerifier.create(this.passwordChecker.check("123456"))
    -			.assertNext((check) -> assertThat(check.isCompromised()).isFalse())
    -			.verifyComplete();
    -	}
    -
    -}
    diff --git a/web/src/test/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixInTests.java b/web/src/test/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixInTests.java
    deleted file mode 100644
    index 703811658c6..00000000000
    --- a/web/src/test/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixInTests.java
    +++ /dev/null
    @@ -1,77 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.jackson2;
    -
    -import org.junit.jupiter.api.BeforeEach;
    -import org.junit.jupiter.api.Test;
    -import org.skyscreamer.jsonassert.JSONAssert;
    -
    -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    -import org.springframework.security.core.Authentication;
    -import org.springframework.security.core.authority.AuthorityUtils;
    -import org.springframework.security.jackson2.AbstractMixinTests;
    -import org.springframework.security.jackson2.SimpleGrantedAuthorityMixinTests;
    -import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority;
    -
    -import static org.assertj.core.api.Assertions.assertThat;
    -
    -/**
    - * @author Markus Heiden
    - * @since 6.3
    - */
    -public class SwitchUserGrantedAuthorityMixInTests extends AbstractMixinTests {
    -
    -	// language=JSON
    -	private static final String SWITCH_JSON = """
    -			{
    -				"@class": "org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority",
    -				"role": "switched",
    -				"source": {
    -					"@class": "org.springframework.security.authentication.UsernamePasswordAuthenticationToken",
    -					"principal": "principal",
    -					"credentials": "credentials",
    -					"authenticated": true,
    -					"details": null,
    -					"authorities": %s
    -				}
    -			}
    -			""".formatted(SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON);
    -
    -	private Authentication source;
    -
    -	@BeforeEach
    -	public void setUp() {
    -		this.source = new UsernamePasswordAuthenticationToken("principal", "credentials",
    -				AuthorityUtils.createAuthorityList("ROLE_USER"));
    -	}
    -
    -	@Test
    -	public void serializeWhenPrincipalCredentialsAuthoritiesThenSuccess() throws Exception {
    -		SwitchUserGrantedAuthority expected = new SwitchUserGrantedAuthority("switched", this.source);
    -		String serializedJson = this.mapper.writeValueAsString(expected);
    -		JSONAssert.assertEquals(SWITCH_JSON, serializedJson, true);
    -	}
    -
    -	@Test
    -	public void deserializeWhenSourceIsUsernamePasswordAuthenticationTokenThenSuccess() throws Exception {
    -		SwitchUserGrantedAuthority deserialized = this.mapper.readValue(SWITCH_JSON, SwitchUserGrantedAuthority.class);
    -		assertThat(deserialized).isNotNull();
    -		assertThat(deserialized.getAuthority()).isEqualTo("switched");
    -		assertThat(deserialized.getSource()).isEqualTo(this.source);
    -	}
    -
    -}
    diff --git a/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java b/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java
    index 80c33ac9eca..f04b5269e5e 100644
    --- a/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java
    +++ b/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java
    @@ -69,26 +69,9 @@ public void cleanup() {
     		SecurityContextHolder.clearContext();
     	}
     
    -	@Test
    -	public void supportsParameterNoAnnotationWrongType() {
    -		assertThat(this.resolver.supportsParameter(showSecurityContextNoAnnotationTypeMismatch())).isFalse();
    -	}
    -
     	@Test
     	public void supportsParameterNoAnnotation() {
    -		assertThat(this.resolver.supportsParameter(showSecurityContextNoAnnotation())).isTrue();
    -	}
    -
    -	@Test
    -	public void supportsParameterCustomSecurityContextNoAnnotation() {
    -		assertThat(this.resolver.supportsParameter(showSecurityContextWithCustomSecurityContextNoAnnotation()))
    -			.isTrue();
    -	}
    -
    -	@Test
    -	public void supportsParameterNoAnnotationCustomType() {
    -		assertThat(this.resolver.supportsParameter(showSecurityContextWithCustomSecurityContextNoAnnotation()))
    -			.isTrue();
    +		assertThat(this.resolver.supportsParameter(showSecurityContextNoAnnotation())).isFalse();
     	}
     
     	@Test
    @@ -105,24 +88,6 @@ public void resolveArgumentWithCustomSecurityContext() {
     		assertThat(customSecurityContext.getAuthentication().getPrincipal()).isEqualTo(principal);
     	}
     
    -	@Test
    -	public void resolveArgumentWithCustomSecurityContextNoAnnotation() {
    -		String principal = "custom_security_context";
    -		setAuthenticationPrincipalWithCustomSecurityContext(principal);
    -		CustomSecurityContext customSecurityContext = (CustomSecurityContext) this.resolver
    -			.resolveArgument(showSecurityContextWithCustomSecurityContextNoAnnotation(), null, null, null);
    -		assertThat(customSecurityContext.getAuthentication().getPrincipal()).isEqualTo(principal);
    -	}
    -
    -	@Test
    -	public void resolveArgumentWithNoAnnotation() {
    -		String principal = "custom_security_context";
    -		setAuthenticationPrincipal(principal);
    -		SecurityContext securityContext = (SecurityContext) this.resolver
    -			.resolveArgument(showSecurityContextNoAnnotation(), null, null, null);
    -		assertThat(securityContext.getAuthentication().getPrincipal()).isEqualTo(principal);
    -	}
    -
     	@Test
     	public void resolveArgumentWithCustomSecurityContextTypeMatch() {
     		String principal = "custom_security_context_type_match";
    @@ -247,12 +212,8 @@ public void metaAnnotationWhenCurrentSecurityWithErrorOnInvalidTypeThenMisMatch(
     			.resolveArgument(showCurrentSecurityWithErrorOnInvalidTypeMisMatch(), null, null, null));
     	}
     
    -	private MethodParameter showSecurityContextNoAnnotationTypeMismatch() {
    -		return getMethodParameter("showSecurityContextNoAnnotation", String.class);
    -	}
    -
     	private MethodParameter showSecurityContextNoAnnotation() {
    -		return getMethodParameter("showSecurityContextNoAnnotation", SecurityContext.class);
    +		return getMethodParameter("showSecurityContextNoAnnotation", String.class);
     	}
     
     	private MethodParameter showSecurityContextAnnotation() {
    @@ -315,11 +276,6 @@ public MethodParameter showCurrentSecurityWithErrorOnInvalidTypeMisMatch() {
     		return getMethodParameter("showCurrentSecurityWithErrorOnInvalidTypeMisMatch", String.class);
     	}
     
    -	public MethodParameter showSecurityContextWithCustomSecurityContextNoAnnotation() {
    -		return getMethodParameter("showSecurityContextWithCustomSecurityContextNoAnnotation",
    -				CustomSecurityContext.class);
    -	}
    -
     	private MethodParameter getMethodParameter(String methodName, Class... paramTypes) {
     		Method method = ReflectionUtils.findMethod(TestController.class, methodName, paramTypes);
     		return new MethodParameter(method, 0);
    @@ -402,12 +358,6 @@ public void showCurrentSecurityWithErrorOnInvalidTypeMisMatch(
     				@CurrentSecurityWithErrorOnInvalidType String typeMisMatch) {
     		}
     
    -		public void showSecurityContextNoAnnotation(SecurityContext context) {
    -		}
    -
    -		public void showSecurityContextWithCustomSecurityContextNoAnnotation(CustomSecurityContext context) {
    -		}
    -
     	}
     
     	static class CustomSecurityContext implements SecurityContext {
    diff --git a/web/src/test/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolverTests.java b/web/src/test/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolverTests.java
    index 5556a25ed1b..06cc9282bff 100644
    --- a/web/src/test/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolverTests.java
    +++ b/web/src/test/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolverTests.java
    @@ -69,14 +69,6 @@ public class CurrentSecurityContextArgumentResolverTests {
     
     	ResolvableMethod securityContextMethod = ResolvableMethod.on(getClass()).named("securityContext").build();
     
    -	ResolvableMethod securityContextNoAnnotationMethod = ResolvableMethod.on(getClass())
    -		.named("securityContextNoAnnotation")
    -		.build();
    -
    -	ResolvableMethod customSecurityContextNoAnnotationMethod = ResolvableMethod.on(getClass())
    -		.named("customSecurityContextNoAnnotation")
    -		.build();
    -
     	ResolvableMethod securityContextWithAuthentication = ResolvableMethod.on(getClass())
     		.named("securityContextWithAuthentication")
     		.build();
    @@ -95,19 +87,6 @@ public void supportsParameterCurrentSecurityContext() {
     			.isTrue();
     	}
     
    -	@Test
    -	public void supportsParameterCurrentSecurityContextNoAnnotation() {
    -		assertThat(this.resolver
    -			.supportsParameter(this.securityContextNoAnnotationMethod.arg(Mono.class, SecurityContext.class))).isTrue();
    -	}
    -
    -	@Test
    -	public void supportsParameterCurrentCustomSecurityContextNoAnnotation() {
    -		assertThat(this.resolver.supportsParameter(
    -				this.customSecurityContextNoAnnotationMethod.arg(Mono.class, CustomSecurityContext.class)))
    -			.isTrue();
    -	}
    -
     	@Test
     	public void supportsParameterWithAuthentication() {
     		assertThat(this.resolver
    @@ -144,40 +123,6 @@ public void resolveArgumentWithSecurityContext() {
     		ReactiveSecurityContextHolder.clearContext();
     	}
     
    -	@Test
    -	public void resolveArgumentWithSecurityContextNoAnnotation() {
    -		MethodParameter parameter = ResolvableMethod.on(getClass())
    -			.named("securityContextNoAnnotation")
    -			.build()
    -			.arg(Mono.class, SecurityContext.class);
    -		Authentication auth = buildAuthenticationWithPrincipal("hello");
    -		Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
    -		Mono argument = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange);
    -		SecurityContext securityContext = (SecurityContext) argument.contextWrite(context)
    -			.cast(Mono.class)
    -			.block()
    -			.block();
    -		assertThat(securityContext.getAuthentication()).isSameAs(auth);
    -		ReactiveSecurityContextHolder.clearContext();
    -	}
    -
    -	@Test
    -	public void resolveArgumentWithCustomSecurityContextNoAnnotation() {
    -		MethodParameter parameter = ResolvableMethod.on(getClass())
    -			.named("customSecurityContextNoAnnotation")
    -			.build()
    -			.arg(Mono.class, CustomSecurityContext.class);
    -		Authentication auth = buildAuthenticationWithPrincipal("hello");
    -		Context context = ReactiveSecurityContextHolder.withSecurityContext(Mono.just(new CustomSecurityContext(auth)));
    -		Mono argument = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange);
    -		CustomSecurityContext securityContext = (CustomSecurityContext) argument.contextWrite(context)
    -			.cast(Mono.class)
    -			.block()
    -			.block();
    -		assertThat(securityContext.getAuthentication()).isSameAs(auth);
    -		ReactiveSecurityContextHolder.clearContext();
    -	}
    -
     	@Test
     	public void resolveArgumentWithCustomSecurityContext() {
     		MethodParameter parameter = ResolvableMethod.on(getClass())
    @@ -405,12 +350,6 @@ public void metaAnnotationWhenCurrentSecurityWithErrorOnInvalidTypeThenMisMatch(
     	void securityContext(@CurrentSecurityContext Mono monoSecurityContext) {
     	}
     
    -	void securityContextNoAnnotation(Mono securityContextMono) {
    -	}
    -
    -	void customSecurityContextNoAnnotation(Mono securityContextMono) {
    -	}
    -
     	void customSecurityContext(@CurrentSecurityContext Mono monoSecurityContext) {
     	}
     
    diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationConverterTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationConverterTests.java
    deleted file mode 100644
    index 6840106870c..00000000000
    --- a/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationConverterTests.java
    +++ /dev/null
    @@ -1,137 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.server.authentication;
    -
    -import org.junit.jupiter.api.Test;
    -import org.junit.jupiter.api.extension.ExtendWith;
    -import org.mockito.junit.jupiter.MockitoExtension;
    -import reactor.core.publisher.Mono;
    -import reactor.test.StepVerifier;
    -
    -import org.springframework.http.HttpHeaders;
    -import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
    -import org.springframework.mock.web.server.MockServerWebExchange;
    -import org.springframework.security.authentication.AbstractAuthenticationToken;
    -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    -import org.springframework.security.core.Authentication;
    -import org.springframework.security.core.AuthenticationException;
    -import org.springframework.security.core.authority.AuthorityUtils;
    -import org.springframework.web.server.ServerWebExchange;
    -
    -import static org.assertj.core.api.Assertions.assertThat;
    -
    -/**
    - * @author DingHao
    - * @since 6.3
    - */
    -@ExtendWith(MockitoExtension.class)
    -public class DelegatingServerAuthenticationConverterTests {
    -
    -	DelegatingServerAuthenticationConverter converter = new DelegatingServerAuthenticationConverter(
    -			new ApkServerAuthenticationConverter(), new ServerHttpBasicAuthenticationConverter());
    -
    -	MockServerHttpRequest.BaseBuilder request = MockServerHttpRequest.get("/");
    -
    -	@Test
    -	public void applyServerHttpBasicAuthenticationConverter() {
    -		Mono result = this.converter.convert(MockServerWebExchange
    -			.from(this.request.header(HttpHeaders.AUTHORIZATION, "Basic dXNlcjpwYXNzd29yZA==").build()));
    -		UsernamePasswordAuthenticationToken authentication = result.cast(UsernamePasswordAuthenticationToken.class)
    -			.block();
    -		assertThat(authentication.getPrincipal()).isEqualTo("user");
    -		assertThat(authentication.getCredentials()).isEqualTo("password");
    -	}
    -
    -	@Test
    -	public void applyApkServerAuthenticationConverter() {
    -		String apk = "123e4567e89b12d3a456426655440000";
    -		Mono result = this.converter
    -			.convert(MockServerWebExchange.from(this.request.header("APK", apk).build()));
    -		ApkTokenAuthenticationToken authentication = result.cast(ApkTokenAuthenticationToken.class).block();
    -		assertThat(authentication.getApk()).isEqualTo(apk);
    -	}
    -
    -	@Test
    -	public void applyServerHttpBasicAuthenticationConverterWhenFirstAuthenticationConverterException() {
    -		this.converter.setContinueOnError(true);
    -		Mono result = this.converter.convert(MockServerWebExchange.from(this.request.header("APK", "")
    -			.header(HttpHeaders.AUTHORIZATION, "Basic dXNlcjpwYXNzd29yZA==")
    -			.build()));
    -		UsernamePasswordAuthenticationToken authentication = result.cast(UsernamePasswordAuthenticationToken.class)
    -			.block();
    -		assertThat(authentication.getPrincipal()).isEqualTo("user");
    -		assertThat(authentication.getCredentials()).isEqualTo("password");
    -	}
    -
    -	@Test
    -	public void applyApkServerAuthenticationConverterThenDelegate2NotInvokedAndError() {
    -		Mono result = this.converter.convert(MockServerWebExchange.from(this.request.header("APK", "")
    -			.header(HttpHeaders.AUTHORIZATION, "Basic dXNlcjpwYXNzd29yZA==")
    -			.build()));
    -		StepVerifier.create(result.cast(ApkTokenAuthenticationToken.class))
    -			.expectError(ApkAuthenticationException.class)
    -			.verify();
    -	}
    -
    -	public static class ApkServerAuthenticationConverter implements ServerAuthenticationConverter {
    -
    -		@Override
    -		public Mono convert(ServerWebExchange exchange) {
    -			return Mono.fromCallable(() -> exchange.getRequest().getHeaders().getFirst("APK")).map((apk) -> {
    -				if (apk.isEmpty()) {
    -					throw new ApkAuthenticationException("apk invalid");
    -				}
    -				return new ApkTokenAuthenticationToken(apk);
    -			});
    -		}
    -
    -	}
    -
    -	public static class ApkTokenAuthenticationToken extends AbstractAuthenticationToken {
    -
    -		private final String apk;
    -
    -		public ApkTokenAuthenticationToken(String apk) {
    -			super(AuthorityUtils.NO_AUTHORITIES);
    -			this.apk = apk;
    -		}
    -
    -		public String getApk() {
    -			return this.apk;
    -		}
    -
    -		@Override
    -		public Object getCredentials() {
    -			return this.getApk();
    -		}
    -
    -		@Override
    -		public Object getPrincipal() {
    -			return this.getApk();
    -		}
    -
    -	}
    -
    -	public static class ApkAuthenticationException extends AuthenticationException {
    -
    -		public ApkAuthenticationException(String msg) {
    -			super(msg);
    -		}
    -
    -	}
    -
    -}
    diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandlerTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandlerTests.java
    index 444e745276a..75852dbccc3 100644
    --- a/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandlerTests.java
    +++ b/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandlerTests.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2023 the original author or authors.
    + * Copyright 2002-2019 the original author or authors.
      *
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
    @@ -17,8 +17,6 @@
     package org.springframework.security.web.server.authentication;
     
     import java.time.Duration;
    -import java.util.Collections;
    -import java.util.List;
     import java.util.concurrent.CountDownLatch;
     import java.util.concurrent.TimeUnit;
     import java.util.concurrent.atomic.AtomicBoolean;
    @@ -76,15 +74,9 @@ public void constructorWhenNullThenIllegalArgumentException() {
     	}
     
     	@Test
    -	public void constructorWhenNullListThenIllegalArgumentException() {
    -		assertThatIllegalArgumentException().isThrownBy(() -> new DelegatingServerAuthenticationSuccessHandler(
    -				(List) null));
    -	}
    -
    -	@Test
    -	public void constructorWhenEmptyListThenIllegalArgumentException() {
    -		assertThatIllegalArgumentException()
    -			.isThrownBy(() -> new DelegatingServerAuthenticationSuccessHandler(Collections.emptyList()));
    +	public void constructorWhenEmptyThenIllegalArgumentException() {
    +		assertThatIllegalArgumentException().isThrownBy(
    +				() -> new DelegatingServerAuthenticationSuccessHandler(new ServerAuthenticationSuccessHandler[0]));
     	}
     
     	@Test
    diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/session/ConcurrentSessionControlServerAuthenticationSuccessHandlerTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/session/ConcurrentSessionControlServerAuthenticationSuccessHandlerTests.java
    deleted file mode 100644
    index b23c93c23cf..00000000000
    --- a/web/src/test/java/org/springframework/security/web/server/authentication/session/ConcurrentSessionControlServerAuthenticationSuccessHandlerTests.java
    +++ /dev/null
    @@ -1,171 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.server.authentication.session;
    -
    -import java.time.Instant;
    -import java.util.Arrays;
    -import java.util.List;
    -
    -import org.junit.jupiter.api.BeforeEach;
    -import org.junit.jupiter.api.Test;
    -import org.mockito.ArgumentCaptor;
    -import reactor.core.publisher.Flux;
    -import reactor.core.publisher.Mono;
    -
    -import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
    -import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
    -import org.springframework.mock.web.server.MockWebSession;
    -import org.springframework.security.authentication.TestAuthentication;
    -import org.springframework.security.core.Authentication;
    -import org.springframework.security.core.session.ReactiveSessionInformation;
    -import org.springframework.security.core.session.ReactiveSessionRegistry;
    -import org.springframework.security.web.server.WebFilterExchange;
    -import org.springframework.security.web.server.authentication.ConcurrentSessionControlServerAuthenticationSuccessHandler;
    -import org.springframework.security.web.server.authentication.MaximumSessionsContext;
    -import org.springframework.security.web.server.authentication.ServerMaximumSessionsExceededHandler;
    -import org.springframework.security.web.server.authentication.SessionLimit;
    -import org.springframework.web.server.ServerWebExchange;
    -import org.springframework.web.server.WebFilterChain;
    -
    -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 ConcurrentSessionControlServerAuthenticationSuccessHandler}.
    - *
    - * @author Marcus da Coregio
    - */
    -class ConcurrentSessionControlServerAuthenticationSuccessHandlerTests {
    -
    -	private ConcurrentSessionControlServerAuthenticationSuccessHandler strategy;
    -
    -	ReactiveSessionRegistry sessionRegistry = mock();
    -
    -	ServerWebExchange exchange = mock();
    -
    -	WebFilterChain chain = mock();
    -
    -	ServerMaximumSessionsExceededHandler handler = mock();
    -
    -	ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(MaximumSessionsContext.class);
    -
    -	@BeforeEach
    -	void setup() {
    -		given(this.exchange.getResponse()).willReturn(new MockServerHttpResponse());
    -		given(this.exchange.getRequest()).willReturn(MockServerHttpRequest.get("/").build());
    -		given(this.exchange.getSession()).willReturn(Mono.just(new MockWebSession()));
    -		given(this.handler.handle(any())).willReturn(Mono.empty());
    -		this.strategy = new ConcurrentSessionControlServerAuthenticationSuccessHandler(this.sessionRegistry,
    -				this.handler);
    -	}
    -
    -	@Test
    -	void constructorWhenNullRegistryThenException() {
    -		assertThatIllegalArgumentException()
    -			.isThrownBy(() -> new ConcurrentSessionControlServerAuthenticationSuccessHandler(null, this.handler))
    -			.withMessage("sessionRegistry cannot be null");
    -	}
    -
    -	@Test
    -	void constructorWhenNullHandlerThenException() {
    -		assertThatIllegalArgumentException()
    -			.isThrownBy(
    -					() -> new ConcurrentSessionControlServerAuthenticationSuccessHandler(this.sessionRegistry, null))
    -			.withMessage("maximumSessionsExceededHandler cannot be null");
    -	}
    -
    -	@Test
    -	void setMaximumSessionsForAuthenticationWhenNullThenException() {
    -		assertThatIllegalArgumentException().isThrownBy(() -> this.strategy.setSessionLimit(null))
    -			.withMessage("sessionLimit cannot be null");
    -	}
    -
    -	@Test
    -	void onAuthenticationWhenSessionLimitIsUnlimitedThenDoNothing() {
    -		ServerMaximumSessionsExceededHandler handler = mock(ServerMaximumSessionsExceededHandler.class);
    -		this.strategy = new ConcurrentSessionControlServerAuthenticationSuccessHandler(this.sessionRegistry, handler);
    -		this.strategy.setSessionLimit(SessionLimit.UNLIMITED);
    -		this.strategy.onAuthenticationSuccess(null, TestAuthentication.authenticatedUser()).block();
    -		verifyNoInteractions(handler, this.sessionRegistry);
    -	}
    -
    -	@Test
    -	void onAuthenticationWhenMaximumSessionsIsOneAndExceededThenHandlerIsCalled() {
    -		Authentication authentication = TestAuthentication.authenticatedUser();
    -		List sessions = Arrays.asList(createSessionInformation("100"),
    -				createSessionInformation("101"));
    -		given(this.sessionRegistry.getAllSessions(authentication.getPrincipal()))
    -			.willReturn(Flux.fromIterable(sessions));
    -		this.strategy.onAuthenticationSuccess(new WebFilterExchange(this.exchange, this.chain), authentication).block();
    -		verify(this.handler).handle(this.contextCaptor.capture());
    -		assertThat(this.contextCaptor.getValue().getMaximumSessionsAllowed()).isEqualTo(1);
    -		assertThat(this.contextCaptor.getValue().getSessions()).isEqualTo(sessions);
    -		assertThat(this.contextCaptor.getValue().getAuthentication()).isEqualTo(authentication);
    -	}
    -
    -	@Test
    -	void onAuthenticationWhenMaximumSessionsIsGreaterThanOneAndExceededThenHandlerIsCalled() {
    -		this.strategy.setSessionLimit(SessionLimit.of(5));
    -		Authentication authentication = TestAuthentication.authenticatedUser();
    -		List sessions = Arrays.asList(createSessionInformation("100"),
    -				createSessionInformation("101"), createSessionInformation("102"), createSessionInformation("103"),
    -				createSessionInformation("104"));
    -		given(this.sessionRegistry.getAllSessions(authentication.getPrincipal()))
    -			.willReturn(Flux.fromIterable(sessions));
    -		this.strategy.onAuthenticationSuccess(new WebFilterExchange(this.exchange, this.chain), authentication).block();
    -		verify(this.handler).handle(this.contextCaptor.capture());
    -		assertThat(this.contextCaptor.getValue().getMaximumSessionsAllowed()).isEqualTo(5);
    -		assertThat(this.contextCaptor.getValue().getSessions()).isEqualTo(sessions);
    -		assertThat(this.contextCaptor.getValue().getAuthentication()).isEqualTo(authentication);
    -	}
    -
    -	@Test
    -	void onAuthenticationWhenMaximumSessionsForUsersAreDifferentThenHandlerIsCalledWhereNeeded() {
    -		Authentication user = TestAuthentication.authenticatedUser();
    -		Authentication admin = TestAuthentication.authenticatedAdmin();
    -		this.strategy.setSessionLimit((authentication) -> {
    -			if (authentication.equals(user)) {
    -				return Mono.just(1);
    -			}
    -			return Mono.just(3);
    -		});
    -
    -		List userSessions = Arrays.asList(createSessionInformation("100"));
    -		List adminSessions = Arrays.asList(createSessionInformation("200"),
    -				createSessionInformation("201"));
    -
    -		given(this.sessionRegistry.getAllSessions(user.getPrincipal())).willReturn(Flux.fromIterable(userSessions));
    -		given(this.sessionRegistry.getAllSessions(admin.getPrincipal())).willReturn(Flux.fromIterable(adminSessions));
    -
    -		this.strategy.onAuthenticationSuccess(new WebFilterExchange(this.exchange, this.chain), user).block();
    -		this.strategy.onAuthenticationSuccess(new WebFilterExchange(this.exchange, this.chain), admin).block();
    -		verify(this.handler).handle(this.contextCaptor.capture());
    -		assertThat(this.contextCaptor.getValue().getMaximumSessionsAllowed()).isEqualTo(1);
    -		assertThat(this.contextCaptor.getValue().getSessions()).isEqualTo(userSessions);
    -		assertThat(this.contextCaptor.getValue().getAuthentication()).isEqualTo(user);
    -	}
    -
    -	private ReactiveSessionInformation createSessionInformation(String sessionId) {
    -		return new ReactiveSessionInformation(sessionId, "principal", Instant.now());
    -	}
    -
    -}
    diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/session/InMemoryReactiveSessionRegistryTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/session/InMemoryReactiveSessionRegistryTests.java
    deleted file mode 100644
    index 630d272b83a..00000000000
    --- a/web/src/test/java/org/springframework/security/web/server/authentication/session/InMemoryReactiveSessionRegistryTests.java
    +++ /dev/null
    @@ -1,106 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.server.authentication.session;
    -
    -import java.time.Instant;
    -import java.time.LocalDate;
    -import java.time.ZoneOffset;
    -import java.util.List;
    -
    -import org.junit.jupiter.api.Test;
    -
    -import org.springframework.security.authentication.TestAuthentication;
    -import org.springframework.security.core.Authentication;
    -import org.springframework.security.core.session.InMemoryReactiveSessionRegistry;
    -import org.springframework.security.core.session.ReactiveSessionInformation;
    -
    -import static org.assertj.core.api.Assertions.assertThat;
    -
    -/**
    - * Tests for {@link InMemoryReactiveSessionRegistry}.
    - */
    -class InMemoryReactiveSessionRegistryTests {
    -
    -	InMemoryReactiveSessionRegistry sessionRegistry = new InMemoryReactiveSessionRegistry();
    -
    -	Instant now = LocalDate.of(2023, 11, 21).atStartOfDay().toInstant(ZoneOffset.UTC);
    -
    -	@Test
    -	void saveWhenPrincipalThenRegisterPrincipalSession() {
    -		Authentication authentication = TestAuthentication.authenticatedUser();
    -		ReactiveSessionInformation sessionInformation = new ReactiveSessionInformation(authentication.getPrincipal(),
    -				"1234", this.now);
    -		this.sessionRegistry.saveSessionInformation(sessionInformation).block();
    -		List principalSessions = this.sessionRegistry
    -			.getAllSessions(authentication.getPrincipal())
    -			.collectList()
    -			.block();
    -		assertThat(principalSessions).hasSize(1);
    -		assertThat(this.sessionRegistry.getSessionInformation("1234").block()).isNotNull();
    -	}
    -
    -	@Test
    -	void getAllSessionsWhenMultipleSessionsThenReturnAll() {
    -		Authentication authentication = TestAuthentication.authenticatedUser();
    -		ReactiveSessionInformation sessionInformation1 = new ReactiveSessionInformation(authentication.getPrincipal(),
    -				"1234", this.now);
    -		ReactiveSessionInformation sessionInformation2 = new ReactiveSessionInformation(authentication.getPrincipal(),
    -				"4321", this.now);
    -		ReactiveSessionInformation sessionInformation3 = new ReactiveSessionInformation(authentication.getPrincipal(),
    -				"9876", this.now);
    -		this.sessionRegistry.saveSessionInformation(sessionInformation1).block();
    -		this.sessionRegistry.saveSessionInformation(sessionInformation2).block();
    -		this.sessionRegistry.saveSessionInformation(sessionInformation3).block();
    -		List sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal())
    -			.collectList()
    -			.block();
    -		assertThat(sessions).hasSize(3);
    -		assertThat(this.sessionRegistry.getSessionInformation("1234").block()).isNotNull();
    -		assertThat(this.sessionRegistry.getSessionInformation("4321").block()).isNotNull();
    -		assertThat(this.sessionRegistry.getSessionInformation("9876").block()).isNotNull();
    -	}
    -
    -	@Test
    -	void removeSessionInformationThenSessionIsRemoved() {
    -		Authentication authentication = TestAuthentication.authenticatedUser();
    -		ReactiveSessionInformation sessionInformation = new ReactiveSessionInformation(authentication.getPrincipal(),
    -				"1234", this.now);
    -		this.sessionRegistry.saveSessionInformation(sessionInformation).block();
    -		this.sessionRegistry.removeSessionInformation("1234").block();
    -		List sessions = this.sessionRegistry.getAllSessions(authentication.getName())
    -			.collectList()
    -			.block();
    -		assertThat(this.sessionRegistry.getSessionInformation("1234").block()).isNull();
    -		assertThat(sessions).isEmpty();
    -	}
    -
    -	@Test
    -	void updateLastAccessTimeThenUpdated() {
    -		Authentication authentication = TestAuthentication.authenticatedUser();
    -		ReactiveSessionInformation sessionInformation = new ReactiveSessionInformation(authentication.getPrincipal(),
    -				"1234", this.now);
    -		this.sessionRegistry.saveSessionInformation(sessionInformation).block();
    -		ReactiveSessionInformation saved = this.sessionRegistry.getSessionInformation("1234").block();
    -		assertThat(saved.getLastAccessTime()).isNotNull();
    -		Instant lastAccessTimeBefore = saved.getLastAccessTime();
    -		this.sessionRegistry.updateLastAccessTime("1234").block();
    -		saved = this.sessionRegistry.getSessionInformation("1234").block();
    -		assertThat(saved.getLastAccessTime()).isNotNull();
    -		assertThat(saved.getLastAccessTime()).isAfter(lastAccessTimeBefore);
    -	}
    -
    -}
    diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/session/InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/session/InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests.java
    deleted file mode 100644
    index eb1db969c31..00000000000
    --- a/web/src/test/java/org/springframework/security/web/server/authentication/session/InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests.java
    +++ /dev/null
    @@ -1,115 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.server.authentication.session;
    -
    -import java.time.Instant;
    -import java.util.List;
    -
    -import org.junit.jupiter.api.BeforeEach;
    -import org.junit.jupiter.api.Test;
    -import reactor.core.publisher.Mono;
    -
    -import org.springframework.security.core.Authentication;
    -import org.springframework.security.core.session.ReactiveSessionInformation;
    -import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler;
    -import org.springframework.security.web.server.authentication.MaximumSessionsContext;
    -import org.springframework.web.server.session.InMemoryWebSessionStore;
    -import org.springframework.web.server.session.WebSessionStore;
    -
    -import static org.mockito.BDDMockito.given;
    -import static org.mockito.Mockito.atLeastOnce;
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.spy;
    -import static org.mockito.Mockito.verify;
    -import static org.mockito.Mockito.verifyNoMoreInteractions;
    -
    -/**
    - * Tests for {@link InvalidateLeastUsedServerMaximumSessionsExceededHandler}
    - *
    - * @author Marcus da Coregio
    - */
    -class InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests {
    -
    -	InvalidateLeastUsedServerMaximumSessionsExceededHandler handler;
    -
    -	WebSessionStore webSessionStore = spy(new InMemoryWebSessionStore());
    -
    -	@BeforeEach
    -	void setup() {
    -		this.handler = new InvalidateLeastUsedServerMaximumSessionsExceededHandler(this.webSessionStore);
    -	}
    -
    -	@Test
    -	void handleWhenInvokedThenInvalidatesLeastRecentlyUsedSessions() {
    -		ReactiveSessionInformation session1 = mock(ReactiveSessionInformation.class);
    -		ReactiveSessionInformation session2 = mock(ReactiveSessionInformation.class);
    -		given(session1.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760010L));
    -		given(session2.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760000L));
    -		given(session2.getSessionId()).willReturn("session2");
    -		given(session2.invalidate()).willReturn(Mono.empty());
    -
    -		MaximumSessionsContext context = new MaximumSessionsContext(mock(Authentication.class),
    -				List.of(session1, session2), 2, null);
    -
    -		this.handler.handle(context).block();
    -
    -		verify(session2).invalidate();
    -		verify(session1).getLastAccessTime(); // used by comparator to sort the sessions
    -		verify(session2).getLastAccessTime(); // used by comparator to sort the sessions
    -		verify(session2).getSessionId(); // used to invalidate session against the
    -		// WebSessionStore
    -		verify(this.webSessionStore).removeSession("session2");
    -		verifyNoMoreInteractions(this.webSessionStore);
    -		verifyNoMoreInteractions(session2);
    -		verifyNoMoreInteractions(session1);
    -	}
    -
    -	@Test
    -	void handleWhenMoreThanOneSessionToInvalidateThenInvalidatesAllOfThem() {
    -		ReactiveSessionInformation session1 = mock(ReactiveSessionInformation.class);
    -		ReactiveSessionInformation session2 = mock(ReactiveSessionInformation.class);
    -		ReactiveSessionInformation session3 = mock(ReactiveSessionInformation.class);
    -		given(session1.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760010L));
    -		given(session2.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760020L));
    -		given(session3.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760030L));
    -		given(session1.invalidate()).willReturn(Mono.empty());
    -		given(session2.invalidate()).willReturn(Mono.empty());
    -		given(session1.getSessionId()).willReturn("session1");
    -		given(session2.getSessionId()).willReturn("session2");
    -
    -		MaximumSessionsContext context = new MaximumSessionsContext(mock(Authentication.class),
    -				List.of(session1, session2, session3), 2, null);
    -		this.handler.handle(context).block();
    -
    -		// @formatter:off
    -		verify(session1).invalidate();
    -		verify(session2).invalidate();
    -		verify(session1).getSessionId();
    -		verify(session2).getSessionId();
    -		verify(session1, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
    -		verify(session2, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
    -		verify(session3, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
    -		verify(this.webSessionStore).removeSession("session1");
    -		verify(this.webSessionStore).removeSession("session2");
    -		verifyNoMoreInteractions(this.webSessionStore);
    -		verifyNoMoreInteractions(session1);
    -		verifyNoMoreInteractions(session2);
    -		verifyNoMoreInteractions(session3);
    -		// @formatter:on
    -	}
    -
    -}
    diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/session/PreventLoginServerMaximumSessionsExceededHandlerTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/session/PreventLoginServerMaximumSessionsExceededHandlerTests.java
    deleted file mode 100644
    index 68f1f096508..00000000000
    --- a/web/src/test/java/org/springframework/security/web/server/authentication/session/PreventLoginServerMaximumSessionsExceededHandlerTests.java
    +++ /dev/null
    @@ -1,57 +0,0 @@
    -/*
    - * Copyright 2002-2024 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *      https://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package org.springframework.security.web.server.authentication.session;
    -
    -import java.util.Collections;
    -
    -import org.junit.jupiter.api.Test;
    -import reactor.core.publisher.Mono;
    -import reactor.test.StepVerifier;
    -
    -import org.springframework.security.authentication.TestAuthentication;
    -import org.springframework.security.web.authentication.session.SessionAuthenticationException;
    -import org.springframework.security.web.server.authentication.MaximumSessionsContext;
    -import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler;
    -import org.springframework.web.server.WebSession;
    -
    -import static org.assertj.core.api.Assertions.assertThat;
    -import static org.mockito.BDDMockito.given;
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.verify;
    -
    -/**
    - * Tests for {@link PreventLoginServerMaximumSessionsExceededHandler}.
    - *
    - * @author Marcus da Coregio
    - */
    -class PreventLoginServerMaximumSessionsExceededHandlerTests {
    -
    -	@Test
    -	void handleWhenInvokedThenInvalidateWebSessionAndThrowsSessionAuthenticationException() {
    -		PreventLoginServerMaximumSessionsExceededHandler handler = new PreventLoginServerMaximumSessionsExceededHandler();
    -		WebSession webSession = mock();
    -		given(webSession.invalidate()).willReturn(Mono.empty());
    -		MaximumSessionsContext context = new MaximumSessionsContext(TestAuthentication.authenticatedUser(),
    -				Collections.emptyList(), 1, webSession);
    -		StepVerifier.create(handler.handle(context)).expectErrorSatisfies((ex) -> {
    -			assertThat(ex).isInstanceOf(SessionAuthenticationException.class);
    -			assertThat(ex.getMessage()).isEqualTo("Maximum sessions exceeded");
    -		}).verify();
    -		verify(webSession).invalidate();
    -	}
    -
    -}
    diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/session/RegisterSessionServerAuthenticationSuccessHandlerTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/session/RegisterSessionServerAuthenticationSuccessHandlerTests.java
    deleted file mode 100644
    index 1dbe3a6119c..00000000000
    --- a/web/src/test/java/org/springframework/security/web/server/authentication/session/RegisterSessionServerAuthenticationSuccessHandlerTests.java
    +++ /dev/null
    @@ -1,84 +0,0 @@
    -/*
    - * Copyright 2002-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.web.server.authentication.session;
    -
    -import org.junit.jupiter.api.Test;
    -import org.junit.jupiter.api.extension.ExtendWith;
    -import org.mockito.ArgumentCaptor;
    -import org.mockito.InjectMocks;
    -import org.mockito.Mock;
    -import org.mockito.junit.jupiter.MockitoExtension;
    -import reactor.core.publisher.Mono;
    -
    -import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
    -import org.springframework.mock.web.server.MockServerWebExchange;
    -import org.springframework.mock.web.server.MockWebSession;
    -import org.springframework.security.authentication.TestAuthentication;
    -import org.springframework.security.core.Authentication;
    -import org.springframework.security.core.session.ReactiveSessionInformation;
    -import org.springframework.security.core.session.ReactiveSessionRegistry;
    -import org.springframework.security.web.server.WebFilterExchange;
    -import org.springframework.security.web.server.authentication.RegisterSessionServerAuthenticationSuccessHandler;
    -import org.springframework.web.server.ServerWebExchange;
    -import org.springframework.web.server.WebFilterChain;
    -import org.springframework.web.server.WebSession;
    -
    -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.verify;
    -
    -@ExtendWith(MockitoExtension.class)
    -class RegisterSessionServerAuthenticationSuccessHandlerTests {
    -
    -	@InjectMocks
    -	RegisterSessionServerAuthenticationSuccessHandler strategy;
    -
    -	@Mock
    -	ReactiveSessionRegistry sessionRegistry;
    -
    -	@Mock
    -	WebFilterChain filterChain;
    -
    -	WebSession session = new MockWebSession();
    -
    -	ServerWebExchange serverWebExchange = MockServerWebExchange.builder(MockServerHttpRequest.get(""))
    -		.session(this.session)
    -		.build();
    -
    -	@Test
    -	void constructorWhenSessionRegistryNullThenException() {
    -		assertThatIllegalArgumentException()
    -			.isThrownBy(() -> new RegisterSessionServerAuthenticationSuccessHandler(null))
    -			.withMessage("sessionRegistry cannot be null");
    -	}
    -
    -	@Test
    -	void onAuthenticationWhenSessionExistsThenSaveSessionInformation() {
    -		given(this.sessionRegistry.saveSessionInformation(any())).willReturn(Mono.empty());
    -		WebFilterExchange webFilterExchange = new WebFilterExchange(this.serverWebExchange, this.filterChain);
    -		Authentication authentication = TestAuthentication.authenticatedUser();
    -		this.strategy.onAuthenticationSuccess(webFilterExchange, authentication).block();
    -		ArgumentCaptor captor = ArgumentCaptor.forClass(ReactiveSessionInformation.class);
    -		verify(this.sessionRegistry).saveSessionInformation(captor.capture());
    -		assertThat(captor.getValue().getSessionId()).isEqualTo(this.session.getId());
    -		assertThat(captor.getValue().getLastAccessTime()).isEqualTo(this.session.getLastAccessTime());
    -		assertThat(captor.getValue().getPrincipal()).isEqualTo(authentication.getPrincipal());
    -	}
    -
    -}
    diff --git a/web/src/test/java/org/springframework/security/web/util/matcher/IpAddressMatcherTests.java b/web/src/test/java/org/springframework/security/web/util/matcher/IpAddressMatcherTests.java
    index 17c2bbadb3a..0362917be13 100644
    --- a/web/src/test/java/org/springframework/security/web/util/matcher/IpAddressMatcherTests.java
    +++ b/web/src/test/java/org/springframework/security/web/util/matcher/IpAddressMatcherTests.java
    @@ -105,10 +105,4 @@ public void ipv6RequiredAddressMaskTooLongThenIllegalArgumentException() {
     					"fe80::21f:5bff:fe33:bd68", 129));
     	}
     
    -	@Test
    -	public void invalidAddressThenIllegalArgumentException() {
    -		assertThatIllegalArgumentException().isThrownBy(() -> new IpAddressMatcher("invalid-ip"))
    -			.withMessage("ipAddress must start with a [, :, or a hexadecimal digit");
    -	}
    -
     }