From c385a59b68466d59a813f3d2f7744f30474abf5b Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Thu, 24 Apr 2025 19:49:20 -0500 Subject: [PATCH 001/149] Improve SchemaZipPlugin Error Message Signed-off-by: Rob Winch <362503+rwinch@users.noreply.github.com> --- .../io/spring/gradle/convention/SchemaZipPlugin.groovy | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy index 3fccb2aef8..cb1fb3b8a4 100644 --- a/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy @@ -32,10 +32,13 @@ public class SchemaZipPlugin implements Plugin { for (def key : schemas.keySet()) { def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1') assert shortName != key + def schemaResourceName = schemas.get(key) File xsdFile = module.sourceSets.main.resources.find { - it.path.endsWith(schemas.get(key)) + it.path.endsWith(schemaResourceName) + } + if (xsdFile == null) { + throw new IllegalStateException("Could not find schema file for resource name " + schemaResourceName + " in src/main/resources") } - assert xsdFile != null schemaZip.into (shortName) { duplicatesStrategy 'exclude' from xsdFile.path From 5abbcecccc1e0b1795f28eaa9dde02cc729145fe Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Thu, 24 Apr 2025 19:48:50 -0500 Subject: [PATCH 002/149] Update to 7.0.0-SNAPSHOT Signed-off-by: Rob Winch <362503+rwinch@users.noreply.github.com> --- .github/dependabot.yml | 26 + .github/workflows/release-scheduler.yml | 2 +- .../config/SecurityNamespaceHandler.java | 4 +- .../main/resources/META-INF/spring.schemas | 6 +- .../security/config/spring-security-7.0.rnc | 1355 ++++++ .../security/config/spring-security-7.0.xsd | 3837 +++++++++++++++++ ...gSecurityCoreVersionSerializableTests.java | 6 + .../config/doc/XsdDocumentedTests.java | 6 +- ...ty.access.AccessDeniedException.serialized | Bin 0 -> 16530 bytes ...s.AuthorizationServiceException.serialized | Bin 0 -> 16610 bytes ....security.access.SecurityConfig.serialized | Bin 0 -> 109 bytes ...s.CycleInRoleHierarchyException.serialized | Bin 0 -> 11098 bytes ...access.intercept.RunAsUserToken.serialized | Bin 0 -> 1379 bytes ...ication.AccountExpiredException.serialized | Bin 0 -> 16839 bytes ...on.AnonymousAuthenticationToken.serialized | Bin 0 -> 787 bytes ...ionCredentialsNotFoundException.serialized | Bin 0 -> 16777 bytes ....AuthenticationServiceException.serialized | Bin 0 -> 16765 bytes ...ication.BadCredentialsException.serialized | Bin 0 -> 16758 bytes ...ion.CredentialsExpiredException.serialized | Bin 0 -> 16843 bytes ...uthentication.DisabledException.serialized | Bin 0 -> 16833 bytes ...fficientAuthenticationException.serialized | Bin 0 -> 16770 bytes ...lAuthenticationServiceException.serialized | Bin 0 -> 16862 bytes ....authentication.LockedException.serialized | Bin 0 -> 16831 bytes ...ation.ProviderNotFoundException.serialized | Bin 0 -> 11213 bytes ...n.RememberMeAuthenticationToken.serialized | Bin 0 -> 1200 bytes ...tion.TestingAuthenticationToken.serialized | Bin 0 -> 534 bytes ...namePasswordAuthenticationToken.serialized | Bin 0 -> 1118 bytes ...ationFailureBadCredentialsEvent.serialized | Bin 0 -> 11700 bytes ...nFailureCredentialsExpiredEvent.serialized | Bin 0 -> 11789 bytes ...henticationFailureDisabledEvent.serialized | Bin 0 -> 11769 bytes ...thenticationFailureExpiredEvent.serialized | Bin 0 -> 11774 bytes ...uthenticationFailureLockedEvent.serialized | Bin 0 -> 11765 bytes ...ionFailureProviderNotFoundEvent.serialized | Bin 0 -> 11704 bytes ...ationFailureProxyUntrustedEvent.serialized | Bin 0 -> 11707 bytes ...ionFailureServiceExceptionEvent.serialized | Bin 0 -> 11709 bytes ...vent.AuthenticationSuccessEvent.serialized | Bin 0 -> 304 bytes ...ctiveAuthenticationSuccessEvent.serialized | Bin 0 -> 414 bytes ...cation.event.LogoutSuccessEvent.serialized | Bin 0 -> 296 bytes ...on.jaas.JaasAuthenticationToken.serialized | Bin 0 -> 1205 bytes ...ation.jaas.JaasGrantedAuthority.serialized | Bin 0 -> 166 bytes ...t.JaasAuthenticationFailedEvent.serialized | Bin 0 -> 11326 bytes ....JaasAuthenticationSuccessEvent.serialized | Bin 0 -> 314 bytes ...ication.ott.DefaultOneTimeToken.serialized | Bin 0 -> 258 bytes ...tt.InvalidOneTimeTokenException.serialized | Bin 0 -> 11220 bytes ...OneTimeTokenAuthenticationToken.serialized | Bin 0 -> 699 bytes ...rd.CompromisedPasswordException.serialized | Bin 0 -> 16772 bytes ....AuthorityAuthorizationDecision.serialized | Bin 0 -> 400 bytes ...orization.AuthorizationDecision.serialized | Bin 0 -> 96 bytes ...on.AuthorizationDeniedException.serialized | Bin 0 -> 11307 bytes ...zation.event.AuthorizationEvent.serialized | Bin 0 -> 1581 bytes ...event.AuthorizationGrantedEvent.serialized | Bin 0 -> 1670 bytes ...CasAssertionAuthenticationToken.serialized | Bin 0 -> 1454 bytes ...tication.CasAuthenticationToken.serialized | Bin 0 -> 2094 bytes ...erviceTicketAuthenticationToken.serialized | Bin 0 -> 552 bytes ...nnotation.AlreadyBuiltException.serialized | Bin 0 -> 11073 bytes ...uthority.SimpleGrantedAuthority.serialized | Bin 0 -> 125 bytes ...ore.context.SecurityContextImpl.serialized | Bin 0 -> 153 bytes ...ontext.TransientSecurityContext.serialized | Bin 0 -> 1294 bytes ...re.session.AbstractSessionEvent.serialized | Bin 0 -> 198 bytes ...sion.ReactiveSessionInformation.serialized | Bin 0 -> 867 bytes ...core.session.SessionInformation.serialized | Bin 0 -> 849 bytes ...etails.User$AuthorityComparator.serialized | Bin 0 -> 91 bytes ....security.core.userdetails.User.serialized | Bin 0 -> 299 bytes ...tails.UsernameNotFoundException.serialized | Bin 0 -> 16762 bytes ...p.ppolicy.PasswordPolicyControl.serialized | Bin 0 -> 96 bytes ...ppolicy.PasswordPolicyException.serialized | Bin 0 -> 11328 bytes ...y.PasswordPolicyResponseControl.serialized | Bin 0 -> 506 bytes ....ldap.userdetails.InetOrgPerson.serialized | Bin 0 -> 1199 bytes ....ldap.userdetails.LdapAuthority.serialized | Bin 0 -> 265 bytes ...userdetails.LdapUserDetailsImpl.serialized | Bin 0 -> 401 bytes ...ecurity.ldap.userdetails.Person.serialized | Bin 0 -> 656 bytes ...nt.ClientAuthorizationException.serialized | Bin 0 -> 16940 bytes ...tAuthorizationRequiredException.serialized | Bin 0 -> 11626 bytes ...2.client.OAuth2AuthorizedClient.serialized | Bin 0 -> 3455 bytes ...client.OAuth2AuthorizedClientId.serialized | Bin 0 -> 171 bytes ...ation.OAuth2AuthenticationToken.serialized | Bin 0 -> 1657 bytes ...rizationCodeAuthenticationToken.serialized | Bin 0 -> 5546 bytes ....OAuth2LoginAuthenticationToken.serialized | Bin 0 -> 5624 bytes ...2AuthorizedClientRefreshedEvent.serialized | Bin 0 -> 3632 bytes ...on.event.OidcUserRefreshedEvent.serialized | Bin 0 -> 3770 bytes ...tication.logout.OidcLogoutToken.serialized | Bin 0 -> 895 bytes ....session.OidcSessionInformation.serialized | Bin 0 -> 2430 bytes ...tion.ClientRegistration$Builder.serialized | Bin 0 -> 1845 bytes ...ientRegistration$ClientSettings.serialized | Bin 0 -> 129 bytes ...registration.ClientRegistration.serialized | Bin 0 -> 2471 bytes ...auth2.core.AuthenticationMethod.serialized | Bin 0 -> 123 bytes ...th2.core.AuthorizationGrantType.serialized | Bin 0 -> 121 bytes ...core.ClientAuthenticationMethod.serialized | Bin 0 -> 126 bytes ...ultOAuth2AuthenticatedPrincipal.serialized | Bin 0 -> 1225 bytes ...ore.OAuth2AccessToken$TokenType.serialized | Bin 0 -> 126 bytes ...y.oauth2.core.OAuth2AccessToken.serialized | Bin 0 -> 733 bytes ...e.OAuth2AuthenticationException.serialized | Bin 0 -> 16972 bytes ...re.OAuth2AuthorizationException.serialized | Bin 0 -> 16821 bytes ...ty.oauth2.core.OAuth2DeviceCode.serialized | Bin 0 -> 313 bytes ...ecurity.oauth2.core.OAuth2Error.serialized | Bin 0 -> 159 bytes ....oauth2.core.OAuth2RefreshToken.serialized | Bin 0 -> 322 bytes ...rity.oauth2.core.OAuth2UserCode.serialized | Bin 0 -> 311 bytes ...int.OAuth2AuthorizationExchange.serialized | Bin 0 -> 2008 bytes ...oint.OAuth2AuthorizationRequest.serialized | Bin 0 -> 1469 bytes ...int.OAuth2AuthorizationResponse.serialized | Bin 0 -> 462 bytes ...OAuth2AuthorizationResponseType.serialized | Bin 0 -> 140 bytes ...ty.oauth2.core.oidc.OidcIdToken.serialized | Bin 0 -> 682 bytes ...y.oauth2.core.oidc.OidcUserInfo.serialized | Bin 0 -> 328 bytes ....core.oidc.user.DefaultOidcUser.serialized | Bin 0 -> 2051 bytes ...ore.oidc.user.OidcUserAuthority.serialized | Bin 0 -> 1313 bytes ...th2.core.user.DefaultOAuth2User.serialized | Bin 0 -> 968 bytes ...2.core.user.OAuth2UserAuthority.serialized | Bin 0 -> 417 bytes ...rity.oauth2.jwt.BadJwtException.serialized | Bin 0 -> 16587 bytes ...amework.security.oauth2.jwt.Jwt.serialized | Bin 0 -> 831 bytes ...tDecoderInitializationException.serialized | Bin 0 -> 16540 bytes ...oauth2.jwt.JwtEncodingException.serialized | Bin 0 -> 16594 bytes ...ecurity.oauth2.jwt.JwtException.serialized | Bin 0 -> 16519 bytes ...uth2.jwt.JwtValidationException.serialized | Bin 0 -> 11427 bytes ....BearerTokenAuthenticationToken.serialized | Bin 0 -> 781 bytes ...erver.resource.BearerTokenError.serialized | Bin 0 -> 473 bytes ...rce.InvalidBearerTokenException.serialized | Bin 0 -> 17355 bytes ...ation.BearerTokenAuthentication.serialized | Bin 0 -> 3022 bytes ....BearerTokenAuthenticationToken.serialized | Bin 0 -> 684 bytes ...ication.DPoPAuthenticationToken.serialized | Bin 0 -> 756 bytes ...tication.JwtAuthenticationToken.serialized | Bin 0 -> 1554 bytes ...pection.BadOpaqueTokenException.serialized | Bin 0 -> 16665 bytes ...ospectionAuthenticatedPrincipal.serialized | Bin 0 -> 1434 bytes ...on.OAuth2IntrospectionException.serialized | Bin 0 -> 16561 bytes ...curity.provisioning.MutableUser.serialized | Bin 0 -> 190 bytes ...k.security.saml2.Saml2Exception.serialized | Bin 0 -> 16611 bytes ....security.saml2.core.Saml2Error.serialized | Bin 0 -> 145 bytes ....saml2.core.Saml2X509Credential.serialized | Bin 0 -> 1623 bytes ...aultSaml2AuthenticatedPrincipal.serialized | Bin 0 -> 357 bytes ...hentication.Saml2Authentication.serialized | Bin 0 -> 1187 bytes ...on.Saml2AuthenticationException.serialized | Bin 0 -> 17070 bytes ...cation.Saml2AuthenticationToken.serialized | Bin 0 -> 6302 bytes ....Saml2PostAuthenticationRequest.serialized | Bin 0 -> 417 bytes ...l2RedirectAuthenticationRequest.serialized | Bin 0 -> 473 bytes ...ation.logout.Saml2LogoutRequest.serialized | Bin 0 -> 736 bytes ...istration$AssertingPartyDetails.serialized | Bin 0 -> 2621 bytes ...ration.RelyingPartyRegistration.serialized | Bin 0 -> 5860 bytes ...UnreachableFilterChainException.serialized | Bin 0 -> 759 bytes ...cation.WebAuthenticationDetails.serialized | Bin 0 -> 162 bytes ...uthenticatedAuthenticationToken.serialized | Bin 0 -> 1231 bytes ...tedCredentialsNotFoundException.serialized | Bin 0 -> 16834 bytes ...oritiesWebAuthenticationDetails.serialized | Bin 0 -> 385 bytes ...rememberme.CookieTheftException.serialized | Bin 0 -> 11333 bytes ...memberme.InvalidCookieException.serialized | Bin 0 -> 11335 bytes ...memberMeAuthenticationException.serialized | Bin 0 -> 16826 bytes ....SessionAuthenticationException.serialized | Bin 0 -> 11233 bytes ....SessionFixationProtectionEvent.serialized | Bin 0 -> 382 bytes ...r.AuthenticationSwitchUserEvent.serialized | Bin 0 -> 1016 bytes ...user.SwitchUserGrantedAuthority.serialized | Bin 0 -> 203 bytes ...ation.www.NonceExpiredException.serialized | Bin 0 -> 16807 bytes ...security.web.csrf.CsrfException.serialized | Bin 0 -> 11077 bytes ...urity.web.csrf.DefaultCsrfToken.serialized | Bin 0 -> 172 bytes ....csrf.InvalidCsrfTokenException.serialized | Bin 0 -> 11241 bytes ....csrf.MissingCsrfTokenException.serialized | Bin 0 -> 11227 bytes ...rewall.RequestRejectedException.serialized | Bin 0 -> 11020 bytes ...avedrequest.DefaultSavedRequest.serialized | Bin 0 -> 1787 bytes ...ty.web.savedrequest.SavedCookie.serialized | Bin 0 -> 250 bytes ...savedrequest.SimpleSavedRequest.serialized | Bin 0 -> 1022 bytes ...y.web.server.csrf.CsrfException.serialized | Bin 0 -> 11084 bytes ...eb.server.csrf.DefaultCsrfToken.serialized | Bin 0 -> 179 bytes ...ServerExchangeRejectedException.serialized | Bin 0 -> 11034 bytes ...session.HttpSessionCreatedEvent.serialized | Bin 0 -> 354 bytes ...ssion.HttpSessionIdChangedEvent.serialized | Bin 0 -> 421 bytes ....AuthenticatorAssertionResponse.serialized | Bin 0 -> 783 bytes ...thn.api.AuthenticatorAttachment.serialized | Bin 0 -> 130 bytes ...uthn.api.AuthenticatorTransport.serialized | Bin 0 -> 124 bytes ...security.web.webauthn.api.Bytes.serialized | Bin 0 -> 140 bytes ...tensionsClientInput$CredProtect.serialized | Bin 0 -> 507 bytes ...enticationExtensionsClientInput.serialized | Bin 0 -> 733 bytes ...ropertiesOutput$ExtensionOutput.serialized | Bin 0 -> 115 bytes ....api.CredentialPropertiesOutput.serialized | Bin 0 -> 306 bytes ...enticationExtensionsClientInput.serialized | Bin 0 -> 230 bytes ...nticationExtensionsClientInputs.serialized | Bin 0 -> 540 bytes ...ticationExtensionsClientOutputs.serialized | Bin 0 -> 619 bytes ...lePublicKeyCredentialUserEntity.serialized | Bin 0 -> 361 bytes ...ebauthn.api.PublicKeyCredential.serialized | Bin 0 -> 2288 bytes ...i.PublicKeyCredentialDescriptor.serialized | Bin 0 -> 688 bytes ...blicKeyCredentialRequestOptions.serialized | Bin 0 -> 1833 bytes ...thn.api.PublicKeyCredentialType.serialized | Bin 0 -> 132 bytes ...api.UserVerificationRequirement.serialized | Bin 0 -> 134 bytes ...tication.WebAuthnAuthentication.serialized | Bin 0 -> 1185 bytes ...AuthnAuthenticationRequestToken.serialized | Bin 0 -> 4101 bytes ...lyingPartyAuthenticationRequest.serialized | Bin 0 -> 3406 bytes .../servlet/appendix/namespace/index.adoc | 2 +- docs/modules/ROOT/pages/whats-new.adoc | 30 +- git/hooks/prepare-forward-merge | 2 +- gradle.properties | 2 +- .../src/main/resources/META-INF/security.tld | 2 +- 187 files changed, 5240 insertions(+), 40 deletions(-) create mode 100644 config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc create mode 100644 config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.access.AccessDeniedException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.access.AuthorizationServiceException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.access.SecurityConfig.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.access.hierarchicalroles.CycleInRoleHierarchyException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.access.intercept.RunAsUserToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.AccountExpiredException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.AnonymousAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.AuthenticationCredentialsNotFoundException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.AuthenticationServiceException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.BadCredentialsException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.CredentialsExpiredException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.DisabledException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.InsufficientAuthenticationException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.InternalAuthenticationServiceException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.LockedException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.ProviderNotFoundException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.RememberMeAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.TestingAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.UsernamePasswordAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureLockedEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationSuccessEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.LogoutSuccessEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.jaas.JaasAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.jaas.JaasGrantedAuthority.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.ott.DefaultOneTimeToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.ott.InvalidOneTimeTokenException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.password.CompromisedPasswordException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.AuthorityAuthorizationDecision.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.AuthorizationDecision.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.AuthorizationDeniedException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.event.AuthorizationEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.event.AuthorizationGrantedEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.cas.authentication.CasAssertionAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.cas.authentication.CasAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.config.annotation.AlreadyBuiltException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.core.authority.SimpleGrantedAuthority.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.core.context.SecurityContextImpl.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.core.context.TransientSecurityContext.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.core.session.AbstractSessionEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.core.session.ReactiveSessionInformation.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.core.session.SessionInformation.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.core.userdetails.User$AuthorityComparator.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.core.userdetails.User.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.core.userdetails.UsernameNotFoundException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.ppolicy.PasswordPolicyControl.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.ppolicy.PasswordPolicyException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.userdetails.InetOrgPerson.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.userdetails.LdapAuthority.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.userdetails.LdapUserDetailsImpl.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.userdetails.Person.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.ClientAuthorizationException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.ClientAuthorizationRequiredException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.OAuth2AuthorizedClientId.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.event.OAuth2AuthorizedClientRefreshedEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.oidc.authentication.event.OidcUserRefreshedEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.registration.ClientRegistration$ClientSettings.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.registration.ClientRegistration.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.AuthenticationMethod.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.AuthorizationGrantType.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.ClientAuthenticationMethod.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2AccessToken$TokenType.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2AccessToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2AuthenticationException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2AuthorizationException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2DeviceCode.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2Error.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2RefreshToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2UserCode.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.oidc.OidcIdToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.oidc.OidcUserInfo.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.user.DefaultOAuth2User.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.user.OAuth2UserAuthority.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.BadJwtException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.Jwt.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.JwtDecoderInitializationException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.JwtEncodingException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.JwtException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.JwtValidationException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.BearerTokenError.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.InvalidBearerTokenException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.provisioning.MutableUser.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.Saml2Exception.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.core.Saml2Error.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.core.Saml2X509Credential.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2Authentication.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration$AssertingPartyDetails.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.UnreachableFilterChainException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.WebAuthenticationDetails.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.rememberme.CookieTheftException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.rememberme.InvalidCookieException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.session.SessionAuthenticationException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.session.SessionFixationProtectionEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.www.NonceExpiredException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.csrf.CsrfException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.csrf.DefaultCsrfToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.csrf.InvalidCsrfTokenException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.csrf.MissingCsrfTokenException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.firewall.RequestRejectedException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.savedrequest.DefaultSavedRequest.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.savedrequest.SavedCookie.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.savedrequest.SimpleSavedRequest.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.server.csrf.CsrfException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.server.csrf.DefaultCsrfToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.server.firewall.ServerExchangeRejectedException.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.session.HttpSessionCreatedEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.session.HttpSessionIdChangedEvent.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.AuthenticatorAttachment.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.AuthenticatorTransport.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.Bytes.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput$CredProtect.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredentialPropertiesOutput$ExtensionOutput.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredentialPropertiesOutput.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientOutputs.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.PublicKeyCredential.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialType.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.UserVerificationRequirement.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest.serialized diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 79f1d99499..86a24961f5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,32 @@ registries: type: maven-repository url: https://repo.spring.io/milestone updates: + - package-ecosystem: gradle + target-branch: 6.5.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.apache.directory.shared:* + - 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.4.x directory: / diff --git a/.github/workflows/release-scheduler.yml b/.github/workflows/release-scheduler.yml index 8b2f0f1eac..9f0045ba1e 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.4.x, 6.3.x ] + branch: [ main, 6.5.x, 6.4.x, 6.3.x ] runs-on: ubuntu-latest steps: - name: Checkout 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 0baa1e2dc6..7f39e23b6d 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 final class SecurityNamespaceHandler implements NamespaceHandler { 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.5. Please update your schema declarations to the 6.5 schema.", + + "with Spring Security 7.0. Please update your schema declarations to the 7.0 schema.", element); } String name = pc.getDelegate().getLocalName(element); @@ -221,7 +221,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler { private boolean matchesVersionInternal(Element element) { String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"); - return schemaLocation.matches("(?m).*spring-security-6\\.5.*.xsd.*") + return schemaLocation.matches("(?m).*spring-security-7\\.0.*.xsd.*") || schemaLocation.matches("(?m).*spring-security.xsd.*") || !schemaLocation.matches("(?m).*spring-security.*"); } diff --git a/config/src/main/resources/META-INF/spring.schemas b/config/src/main/resources/META-INF/spring.schemas index bd7429a36c..d32871d3c8 100644 --- a/config/src/main/resources/META-INF/spring.schemas +++ b/config/src/main/resources/META-INF/spring.schemas @@ -14,7 +14,8 @@ # limitations under the License. # -http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.5.xsd +http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-7.0.xsd +http\://www.springframework.org/schema/security/spring-security-7.0.xsd=org/springframework/security/config/spring-security-7.0.xsd http\://www.springframework.org/schema/security/spring-security-6.5.xsd=org/springframework/security/config/spring-security-6.5.xsd http\://www.springframework.org/schema/security/spring-security-6.4.xsd=org/springframework/security/config/spring-security-6.4.xsd http\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd @@ -41,7 +42,8 @@ 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.5.xsd +https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-7.0.xsd +https\://www.springframework.org/schema/security/spring-security-7.0.xsd=org/springframework/security/config/spring-security-7.0.xsd https\://www.springframework.org/schema/security/spring-security-6.5.xsd=org/springframework/security/config/spring-security-6.5.xsd https\://www.springframework.org/schema/security/spring-security-6.4.xsd=org/springframework/security/config/spring-security-6.4.xsd https\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc new file mode 100644 index 0000000000..ec51246b6f --- /dev/null +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc @@ -0,0 +1,1355 @@ +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 &= + ## Optional attribute specifying the ID of the RequestMatcher implementation used to decide whether to redirect a request to HTTPS + attribute redirect-to-https-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 &= + ## Allows injection of the SessionLimit instance used by the ConcurrentSessionControlAuthenticationStrategy + attribute max-sessions-ref {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" | "HTTPS_REDIRECT_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" | "DEFAULT_RESOURCES_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-7.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd new file mode 100644 index 0000000000..e254b8488e --- /dev/null +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd @@ -0,0 +1,3837 @@ + + + + + + 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. + + + + + + Optional attribute specifying the ID of the RequestMatcher implementation used to decide + whether to redirect a request to HTTPS + + + + + + 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. + + + + + + Allows injection of the SessionLimit instance used by the + ConcurrentSessionControlAuthenticationStrategy + + + + + + 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 9d439ff0ab..2ee3810cd2 100644 --- a/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java +++ b/config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java @@ -260,6 +260,12 @@ class SpringSecurityCoreVersionSerializableTests { String version = System.getProperty("springSecurityVersion"); String[] parts = version.split("\\."); parts[1] = String.valueOf(Integer.parseInt(parts[1]) - 1); + // FIXME: the 7 should not be hardcoded + if ("7".equals(parts[0]) && "-1".equals(parts[1])) { + // if it is version 7.0.x, the previous version is 6.5.x + parts[0] = String.valueOf(Integer.parseInt(parts[0]) - 1); + parts[1] = "5"; // FIXME: this should not be hard coded + } parts[2] = "x"; return String.join(".", parts); } 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 102033dcd3..ccab6cb535 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.5.xsd"; + String schemaDocumentLocation = "org/springframework/security/config/spring-security-7.0.xsd"; XmlSupport xml = new XmlSupport(); @@ -151,8 +151,8 @@ public class XsdDocumentedTests { .list((dir, name) -> name.endsWith(".xsd")); // @formatter:on assertThat(schemas.length) - .withFailMessage("the count is equal to 27, if not then schemaDocument needs updating") - .isEqualTo(27); + .withFailMessage("the count is equal to 28, if not then schemaDocument needs updating") + .isEqualTo(28); } /** diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.access.AccessDeniedException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.access.AccessDeniedException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..77c4777bd73dcee52f0f4fa7698f65fbf1998190 GIT binary patch literal 16530 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 F{|8&MHMz7Xv!qflF*!N4xL7Y3%6HDs zOUq23^C9;|nf9jwCI&_y2DZeKlA_F{5(Xikti-ZJ{hY+Sbp2qUP+q!qML`JzYguAW GX(|8>RVfkx literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.access.hierarchicalroles.CycleInRoleHierarchyException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.access.hierarchicalroles.CycleInRoleHierarchyException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..e1dd1cc40a26bcb8c63a5e986b39909382668748 GIT binary patch literal 11098 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 PKlZg(eh>T3nP}#}f%Lc6 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.access.intercept.RunAsUserToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.access.intercept.RunAsUserToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..8394c9a73882da2af97a15c33d066ec75d9efa91 GIT binary patch literal 1379 zcmb7E&rj4q82#E^TnOQpMiLKr0^`Ay$Q3UOB9JsJUj zCK?ls(Lca=^>6UtNj>`?$k~erzv%+g)nti1H0|`8?|bjfd;NF>16siishWXK6cf#D zMcQ;Bc{bxqZVY*7LE<#Qj(t59jt&I-{xAq+=~ z##9(axHfP;+$DQtmJ`vOU2E*p$jpZ@DHUrnL3lMaJGL|;hDl*yqENlaiPl&=WZ^|D zA^ZUxfR`y)?vJ{sXHacuqex_WN96>*f4F)emg7+$N}Di3+&&s34f~Eb4Wme7vI{IV zjL$Ag&XETKMs$^!8p62lVoN*}^@?R2*wVn2>gl>@%b0C5(%>{?+Wa1yx&G3w?!-q@uxzN}Zs_+6kx2ilAL$tF{%d<4j^%D#>ZDCu+u; zZ$3*546Uz)OI!8Y(t2ib4>>iJU;fFAyXj>XujbnxW zwzEdiW*VRIOjqN4!2&whtwUs|-@tpnWnjQsT<@KQ&uZh41XUqLr!7<6i?EO-@OUqnUxx PM?%Wv@d;G}Wy|spgi6*U literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.AccountExpiredException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.AccountExpiredException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..ad4812a76de5d41666c0f4535669f1cfe91a67d0 GIT binary patch literal 16839 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 Dh5S$w literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.AnonymousAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.AnonymousAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..e8872447c4e4b79e806c9228b3fd7a09c546b6fa GIT binary patch literal 787 zcmb7?u}<7T5QfLTBR~QP3R0vLNd<@k`9yuy5zyU9mQLb=BBcPW?crE%y=!*IoSgzC zFOWV(@&HlVJVT0f;WeP9Ns-;N#BmV<;bLXaj%U7^|Nn6bGe$#4=|NyJ&E?=wvlK^4 z4+Dd7t~r$f%jpOu@t6@;GH6L9%T(p2H5GrWhA5$WJRkfn%nq^aFmn{byel2^j0uA0 z;W0a94Z-A~(Tk2Tre>j`{UCzKkY+L60&xC){Y^kjhTPEKXRDh( zFFr5%;Dt~ZHbSs;qm9dP({9{fZqnxE7JU4@fAxA?)QcTet;j6Y@}x>jYgW4I#iu{7 zzWiceYv6UEZukc*GOLLIY-=V76SwVgxYF1c)s$KZ zoL03@&PdH7BbeXqg*yj({hi&yaX-G-Ya@*Q_uD&+riI?FMz7LqhZ~|1&{7zcX{^&i#11Zz%?TL~gYedUCSjwB(rE zlwQ@5p5LtWrhfK?ZViJ@Jv!{m{eG)i>z}B)0YG=1eR$zx_YSW6Ly zwZU`M><@4H`z52pU7|D~mZaGkbRTNDk(6S~0O9Y+ETSh~L=W)yq*>ohOszcWj60RO z(;Te~wrCj**HC@x!MO((ZhPb(iu?B^fCl3CZ?C?2vXnkyZze#ng?XO!R;TW~`X%GrIdSQtsC zdTKaysswS(y(y4*pug@mNGF8y%BU3tAt7`Vtwd6Et7$Fi)q4iKNba5pqKCxC#Vj`;1u14ZK_JC}DJ^t}_s5Xl6@6q+8r6^+lKFdQ zpzvX`MG|mN=fD++jZ{*18YgN_nbb==t-O6(pj5JmI27B!ZMPIV!JyY9m}b@U=}Wp{ z)2UZR-KHBl(hn;QFRE4!#6S1BWcKKnVp+aZQ=YG9yEet!HN~6TP#klbwK_?%ax8Mg zAu_VM=hh5~qoH3VqXXS_Qv+xo7L&c3*@JG>s!N-j8#Fh2-VryqDPE;mQ^<=R)|@OL zPVx*qq8V7xwNtL$~%!)Vd0L9+{1p&J?_+7wpO~n z!0=MEW8+|K8vV^t+FHa$rbjRGvY1Mif_TMnv>sm@q_{MOJ?fwIZRVfNgH}V=k?xTb zC*5jFe=lc1@$-W=u9F!6DV8f-gT5~nzJuD>?P(2)03Vbc56M?oPJ0rKR6^3*OXdlJS(~aWE;(zo<9`8dZYo`)761pRG z(p>C6+}a`bs=ZaUnYYn|xn6VJKV^)P{J+>p-J1*X>~rhxD7mxdXd01P|C*_WjZpSV zv49Vj6AsSKxtn)XHdVIrIk1SRckLwV%afBQUVnoTKojDsCeN@(z2Mv&--ePfQ-ER!GdYHTS9kQA$vAK4jZ-Geqa zAId8-UU%S7U>j9>!_gLEqdp2$rKXBwYE|)oUim6MX~8hg_hFazFZsEgdPSh=-_wya zNF5v~H%I$f8;BpU;!|!|jukylJsOT=<(x!Q1EkwT zBv=`ysNZkN{t4YdJ0RPorMke2m0?TrD@+Sb4GnpzWd;uR0=?#KrJ9+ApLg46l`*0F zUNrZix!(p*8btSl2H+z$Kys>{06<kT#JOscnz|mv`99IjEWGO$2XF9L-I942*dc+K1IKx_;Ly=DOJD*Kt(CbuG(x zYF_3kFK_n$1 zF1CN_<*m}9)~g)E%`Ec9-TGwt2Af4CY_Ktj<_wzD8otOZqWJ`xPttMF9`#NewGxjM zmYeBFk5~-_Hu$F(GuH``JF$_gXum_YcG)whUGd|&6)(<9(ZvQM{k4x98s(aK6T%@ncTg#hOV4m(b;^u3zy=uifWN{YFGics9 z&^Znhz5^uRZHH;@-L#hLlC|`Pp)-lRU)g*OQ38kJOqDBIWV*GOx<`~$2i*pp;1qU( zQf5tE{~;n95+Yh3d4b(Vd!5;pVxGoZfUb09hw|T1-ptt06pFDE36Zuje`+yy1T^b! zX)#!Wjg+{p9y-^oU&<)K$8%^h@-4khh}Y!g+k1ip?NnuKj}*LSyR;JLSN&j;LTJAT z`M-JnKf}<^X``GDoqz(hzV2^wF&C<3yzf$?7!0FeUF`zti;Lka~oV`&22Zwknf9MSt4n)8!WN89M#1G>=F zf}lTy*1FNer+ggl(SD7O_xNnZ1a|P&1WZlE4I48Ua{wyrM|To1K4OzNXfSjbl>`3M zHipLxhR@)Oe}2{m_<{jQc=((RFf9++I2i~Zw=wWZ#)3*n7(vlVKj%$yKdN@fVUjsn zfr-6D&fh@(A26Kr-Q-JjS5UXK-g@08|4jpzkiRgc3_-j#Gob?dpBJT>zptz_R;8>D zKfi1XY8M0Og6RxyQSGjHlvZgsWa4PEtFS-ff?)p)Z<{$Px=yyLc%Yd0YkKuoCdpj- z>rSg#9iu!u-Wyc<8NIs5Ql{|;rE%!OrlailPcd3d_YJvnCOVTbtEW$H?pbYo^xj!Sj8NU^Cvd1?9Si}rvPD%D#u zJ;ie3L?@hb31zX;;v5G(=;dXb-}=qW4(26w8@o`F}HfkV#Dx&T;rA*!5gWJ*v?};Ge=En zIU43QWyMN67+Zp7DK&|i)9S=W=}dhHOB)RxdWBGN0xv0IO3D{T6-oPy7NM$nW_R9x zjB0yFb!iXj5nhgYtx(okRXSwNpc}<`C@IR*%-5<}!gaDJN~4KIfCW?oENl%Ls`)QO z5+aJqn>LvO4S*h?RdT`PU0$W8Iy;h7&exAjKyNS6<3hmn^oClLEHEN6@{{7KHY2em zl&*^@c2NQPzeKUP0)3cjf#bSp5|^J<)%famLt6OrRGhR4Mw7XFi9;-Q6eda)jPET1 z+6W-gi{{pLKvZ?c(0()r3V|TdGA)lw%37eFe!)X=pH-KV=KoljR&r1t5& zh&%Pt=Ew758G^`iUeh*WqO={2$$VpQ4RNr;+9-Jr`g~$)LhS?f_9`Yy<=6IuMxQBi z_YpaEIc*nArZO=ZQ|PTZ0p*;^@o(C%Nmz?eLPc6<`8Az=$T+sCC%~T&89WVi-lp9*!qoQg*ohH)98#<->^IsEKyggL;g&gIptx5Ih6Lbx3xi4+GY<*C2W@~E zzgz>w9vcInBus+y)LQ_s)dp}2m`ptdJ7)mYQ!xBY0lZiMaHB;c_f8D(dtw0PIni}< zfYy}NQK)!#PD9d#eq;p#oONOQu^%nzw^0B%ga&(2*Z}%f6S|M0`7oN)qmRU*hB0<5 z2avo;;O7E&0pPuUbMZ8YvORF_h_#=JkH1m?FcvEdR$VOsc&}HXUXFl~CYq^4odB3{ z6RWt^tRew0rU!ug4Z!(5`2ZUBf#di(ZA)_6{NEo94+(~k*%%%x05GQio!&G6Pv9$`w)(?#0ADu%U&YsxXj01J1CsY<0`Rq1-EfD`vxj^N6Tg9m z^RIpQN}iMEO4=CymW|~l#&Q<@&!Bl0jg1AQI;u!^gsCH-PRMmsN})P1mh(KC@1o&Y zBeS}W3TmB+pcBSB!bFu@u=^7oXUI2_10HjWUi*-$P8h23Fgec!a(|H*a&=)Iinb3s}d#Vy&lqT5* z2ZE176ugN)fiI5pI#%5;RLeF(pY($}k2~ z?J2FanOuP>3~&`NT#cWp`!Hu_(iqYtI;a-`^BOeBGL_cF^vPCIwHORuYY#pUpbKkI zGfj30GgUhEdJN*&$ksN{Woiv2W-=2@zV`(Q@PyP}L9Jk#LINgX+mv8b=_(AiG$svP`b3518Iv6T$xh*&;8Z literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.AuthenticationServiceException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.AuthenticationServiceException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..4a1ed3a3e64d0287fc776ce653e8a2acb031bbad GIT binary patch literal 16765 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 QaTB{hI9?O{?i{WCAE~V?>i_@% literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.BadCredentialsException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.BadCredentialsException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..7e447f50ac0cd07ddc6ccea49fe7a6725ea3b1db GIT binary patch literal 16758 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 X95Lp|ye+=QP3!{TxG(74J?j4-vVbQ} literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.CredentialsExpiredException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.CredentialsExpiredException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..32623133d13c2049c084db3ac236a30c4635d3ee GIT binary patch literal 16843 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 Hsn-7hYZ_67 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.DisabledException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.DisabledException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..d98805575991ad7214f2873ac3af498112fbc25d GIT binary patch literal 16833 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 MJ6cTd&dJvQ0GMJ<0RR91 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.InsufficientAuthenticationException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.InsufficientAuthenticationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..c642f5868bddd2a0de35f10acb5d36440e8ce662 GIT binary patch literal 16770 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.InternalAuthenticationServiceException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.InternalAuthenticationServiceException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..5f7b7e17458313f5e2d5aca60b7c352516ab56fa GIT binary patch literal 16862 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.LockedException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.LockedException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..9cd1b8f9edd1c5472f8ce97ba007d2102e53e15d GIT binary patch literal 16831 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 i#*!-)B#F2@+6);#&b{>FmKTnH%D@w&*Yb literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.RememberMeAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.RememberMeAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..6a1c85c45613b871c8e3815758e06a0cf6813f54 GIT binary patch literal 1200 zcmb7@zi$&U6vy9P0-@0K2M8pFGPQ^ST?A{Vw3If|K~d5OsYq1L-D~RR&OU61UO-f3 zVgm_;1VZo!AXfec1}0$UKcF)U1J9SzO8~X1htt`9e*S#l`})H!9McMFQnfvuC@$LT zip97kRmam9bQL!}k9Exk3c~|txD;L;V~ks z<_>tmA_J%WO?IDEA|~3E<<=$!W;Ru@coc+cOB=-k^B;h3?=NkO#c<4p!YY&p&@uGk zl$}cw#ZenL*9Yku9#!UL6cGvOoAsd-qG3GJ_h1{GG;ydx7cH}AL%lpyPd7y@L%z;g zD?*=Z^J{$O%Eup1CtPrRC`PnmU}E4J8)Ma8K0IB1&G|Xl`F!{1g)FHPSgJyr5Y(AG zOI0b>v)R+fU#DJwVs8uJ)S#&O156X@iL+I-Uph2*V4(x4Fl)H2QqODfIEipkF<~&Y z)y|glCR>$CMmXHnhOthXO%nqn^<{ryt=U+prZl z`cPIFOM}%gRH(Dgjgsv&{ZmhCp}l<60v+$i5!wD8IPcbo>*|4ASz)>w)t)!JCjI9G zrB#?@K_I)r+>~M=O}N5v6(;gEUj`k!bT*(J`G*(s8feI#ClaPL zJ<+}>np-SE&vs9wLxc3pD`X<|kJaB1L_Fb?e2; w-6wYn6lI-v{phut-nY-Zj1#7qkpuOkw0(zK4dv9p*h3*@^5~CL14XOyH(S7)F#rGn literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.TestingAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.TestingAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..daa0a4b41e2c6123fb4cbbd1c47deeb89956f501 GIT binary patch literal 534 zcmb7>Jx&8L5Jo49fFKkK3MgqPs5S?HCgLX`twcd2Bs%YU2u_@}@ywEhf}_wPAxaAF zLC+1i17o%zi4YR5Y|mJJ@0*uTSf&6cF7#9K!Ib^$phh@$VVDvYRWLkFRmC@0vMCfB zSEfBAmd4K9ZO;v{gnBg+NHSP20_yloSz_4C2I@|=tt$KN^L&5>?`NLbewihZwUZsfoy$j{MDuXIQD<@l1`O=@pU8Tg$d045f_!3+=PK Ab^rhX literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.UsernamePasswordAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.UsernamePasswordAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..c54f152cfccd18910f7b07d723e685d9f72d7c9c GIT binary patch literal 1118 zcmb7D&uddL9RK#UvlH2Lf{F+AGQoqqg4cQJX05`*j&((-prmvI3Mkj z=cFQt>{c3`T}Itn1gHII$~6(sU=Iod1ND<2s&=e1gt|kJ{Q7cZPuAn*0E%r`nqk6X zMCSJjIdX=`E{>64vPGcb>`XJ8Dp4JcFC|rIaZBh!|VZVo6fuGwY7MMMDE8P z|KdP9I5mFfX6_s;c$+L@y>%p3stgNLjKD;cEDkxUavN3%r7D%~fs(a>=Ge6j=kl{i z(VjoLFcXW}(lV2zBmTGN?2J84=2Nq%Ckf1m;oigY$(#@^;6gsv5BI7i6BY9v?uTK& z(UrG9-+UakykiD#9{(oy{qj1DemwqrHGo0{1?s04GuK!0F58CrDZf8#zI(s_`cZL` zi@PV+de!H`hjzH35={(o%H;ZE-1;TldgANHH=llhVc6t}7OZZFbS(SQ*@(O`QY#rH frrKh5wi;3GNvm1g8aP;fDV5T%riM5uc`yC}AAy9X literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..8dbfc337f644eb9f0a4a7ab6fef20c973fd13344 GIT binary patch literal 11700 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 as1`7OocqAbJuePC_sl!6@0<(fg8u@Z4z;@g literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..f0763d490fabce1b15462c2ad95b6e04ae44f95f GIT binary patch literal 11789 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..9977e555bbf9d12f284a3864512248c56a31d50c GIT binary patch literal 11774 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 A6951J literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..9a132ba89879311a6173594945f785e90a316057 GIT binary patch literal 11704 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..4fc6aa88bfb0a32e67258a9605f5a86c8a16c87a GIT binary patch literal 11707 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..2f3e2c94c9505e99478048207510a3af8d9c664e GIT binary patch literal 11709 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationSuccessEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.AuthenticationSuccessEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..1524fa2be52ec91e6e9ae046842a9e872be2ad35 GIT binary patch literal 304 zcmZ4UmVvdnh`}enC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflv9u&3HLoNyIk6-& zKTj{U49L-QL=g-wO-@cNE_MY;DVu)ZrN6>rH4_6vMG=E98Ac}+mlP!?m!Mh!Hv6Q) z^1qiFY=1(`*1=|Wa(-S(Y6Z}N1qC@!Cxf*nwY-k+a6kKyiGk6JfwLqtH?_DVF}DEd sMA59ovP8YolFS@EuuA`=tkmQZpI%NsiQL*1Agc-(7#OE53bCIL0B5OnWB>pF literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..945c17dd9916a57f8bbfdd367d8cc7a1453defa4 GIT binary patch literal 414 zcmZ4UmVvdnh#@?`C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflv9u&3HLoNyIk6-& zKTj{U49L;*%qvMPN=z=vEK7Ankq<6SPEIW@b_J<=lBhIMBf`^}iGk6Ffjd1l52&Ri zHN~m2gh9|JE3qt5KPNFSUEet;vAEc}qKLtlbayx=6_b>qr@ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.LogoutSuccessEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.event.LogoutSuccessEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..65880d2f3b495fc0996cc3e40895946b73bbd111 GIT binary patch literal 296 zcmZ4UmVvdnh`}YlC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflv9u&3HLoNyIk6-& zKTj{U49L;*$xqKOEeS47PEIW@b_EH!eYP+zP|euR#K2Hd#NbQ1k&a2lB}IwJC5|Ze zfz3Xtu>9|(2HT$yvvshUot&Rnl3G!s=U7mX19d7`Yf{VW_zw5851AMky%;!4GILXl xOA>PnfKC+6N-Rs%D=o>)(F3dWPs&P7F7fH*^pnV~T>-MHfPsN=+M*EqbpSM~ak>Bi literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.jaas.JaasAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.jaas.JaasAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..d433e15704abccae09ca0cc0f736571bbac3c74c GIT binary patch literal 1205 zcmb7D&1(}u6rXHUW06)XRPmr*DtM3$c-2cj7DGmfX{}6*^;uYVBH@@Se7v z&~eta0tO-N!ZWTwFbYDeuuZ88AK+9vw7;V}T#?ZTWI#yYlIlEV>yBH6y|<`y*grn8 zdJvH_%<|a7qS665AGPToElR3d#cH$78QhAxo-qkZIE;6UZh#aZ>?owk-b{riZieY)GW_x6@0Eb$B9ddidw%5lYFcH- za^L0mhpl(-4_-fBnB?N_{93R3T=>uqHg#gCrJytVW88Xqw8InMm|cAO0R~}BBu4Uz sr3(1yOOINl#v--6)pDvWW@oz^m7msYW>7BuGL(RbkCd_8hz~b7iTCrQ2+n{ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..638e87f4f382769600de8bcd527181f5fee8fd72 GIT binary patch literal 11326 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..c18423dae2bf09764e5bde98413c2c59e2dca5c4 GIT binary patch literal 314 zcmZ4UmVvdnh#@q;C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflv9u&3HLoNyIk6-& zKTj_!F|k-LwG1et=LKXsqR0l9CMTyB7rTO_<+;vr|42P&%EZ7>QN-X!H9NqD*NH{H z40-%44`R3uHp7$i^GZ@HO7t8H3UZ*12Ww4gc^%*3e)b^~1EUuMXGvymYH>+oZUNAZ rqFIS$iF&0anK^o3mHtUtsmUciy_|j$xwR`mRuwQXFiu+(Vt)+)9tC&y literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.ott.DefaultOneTimeToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.ott.DefaultOneTimeToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..806fbdee7f93bfb72806442ba61699283c3dff4d GIT binary patch literal 258 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..2f775464d34d123590c04d3624deb825f7034a42 GIT binary patch literal 699 zcmb7CF;5#Y82#=N3MHaNfRIoH6I30liOo_EFXbu8X5{_GPMlf79{758tXAvZ${kLS7#HRMPQc4k&*t~lxe6RK=t0f=57|-) z-{mY47;-~TpBEPI9xldx@IojHdqObYH^yO{+wX7F^~TIB!|9)$e{%(?7dutuJhh@G zdn`4r*^y(<&;HI{{$^Jt@YbPh_%Y@w*wrq(Z|)vf?9;XX#@)u~Whb^U8cch&n~`G$ w@I_ICF^w%nSW6O(rijnA!`QtIc0F;!{X8Z3(8W>T9l=OuP)plQf}x@>&;9@O)c^nh literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.password.CompromisedPasswordException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authentication.password.CompromisedPasswordException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..edee78ff8baceb658e80513e755f380f05a4ced9 GIT binary patch literal 16772 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 F008nhjrITl literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.AuthorizationDecision.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.AuthorizationDecision.serialized new file mode 100644 index 0000000000000000000000000000000000000000..3992b0122aacc8328aaffd7c7538c96b3352f4fe GIT binary patch literal 96 zcmZ4UmVvdnh`}MhC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflv9u&3zbLaRu_QA; oPtOsV=aQP7Sqv1tZ1pggBe9c#iGeYSfjzw_F|Q;wrJ{fl03x|0OaK4? literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.AuthorizationDeniedException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.AuthorizationDeniedException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..6ab17b8908a50c4869612a85b4fa5e81a86ca1f8 GIT binary patch literal 11307 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 JsKJZR{{a$M4441_ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.event.AuthorizationEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.event.AuthorizationEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..54cb962030618297b3c956de083c3a5eafb80361 GIT binary patch literal 1581 zcmb7^O=uHA6vtoEv}q{zODl@hQ!5^17sZpk^urjGr7cYZ8o@HzOyjhho$c&wyREcV z5&QrZ6h)DG@Fe2Zt5*-=q4w;d7cbhg2NirXN!+BC7IO&fW9Gg8{Qtk1w_DKc3mD;| zWcZ$7ZfRPOGF{+e*6^tv2qqT|63DAuuq7fHcMUpEU1-XNz#UXA% zyV`?AX6sE7daW5UPs~6v$D9saTeUK?f#*4liUA8+1oZ<)N*J_Ii@8mUS<_-RXfYd9 zOy~{+p@0)R9mnRbq#-I-Gb^&R?!v8aQ+G0-HscVx04d4J)R&~}VVfQ$*-(-Vy>hHD zLv3l@PmLZbFTaihgdRZb=_~p3C3<*nr=d07MwSXjoT`Ue?C(&&?{Bf-(3%%t?a|X0 z*Vm4q91EJ~i4#x>-4*v7;+D*cihnPa=oY|OyO`BblX>VOw#@@qj&e60dJO5x zLuXX8xIL>%J$2mGFHKTc<@FB_9d$uvsYDQ3y(;2L`vLq`vK0FI8_B>Qo~J*`AxfM_m*o%m5@*karEG>F;%Zp^)auGtbCoNHuLc+ZJgOG Rx01Vn;N~G&s!)Wk{Q-H(GEM*h literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.event.AuthorizationGrantedEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.authorization.event.AuthorizationGrantedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..d2ec05e7e4de6d516e85c84b653300638580e9c2 GIT binary patch literal 1670 zcmbtVO-vI(82ws`r9q)cN;C#fU_6*@G@c|e{8$^w1_-5z)&w%$9qPb#XPKRaf&tN( zpeAaf(P#`8PsVul>eYksfSfVm;zc=o5EH-Iwrq<8V%$TMnST4e_r3RK_T3gVx&j8d zC}^%Dm|d6@q)6wunATisc!J4!jd=0~7i@t@#%+zxQd??$wR>qnY)Q?O`e(%qe@$E* z-v|Sg1PuHiu#b4>+V4ZR9(_W24qNn6w&*6Q0 z)_b63T`Fu$%?NQ_Opd9<3oIJr)6@q430uC0&4!?+Dj=3S1541PLri7QU=FeT4b&+8 zFAVzft`x+OdvsoZdwR)En=N5z$ia~s=G4@oS*l9WRr}b@H%tlc5C<9_dg!RebFNp?nsE&v{5yFunvX`+8pnP zIu8Z9Z_?T$*@xq~Tq zn6Zire%^wX(Hyi9!{DARhq#?8ISkX4gLtr~b7NXL}XsPJ?YfJ_{I8S%rL*QC{I1q2S^U2!dSIb#--qrhYe6uZhb4*Z*RU0ox zo<7@Jx!#~|1+3WrHt_A*tyR~KQ3Jf_5J4mtfqn>E&Vu65QIgPd)8^b+td1%nq5!dV o@4hxwj#A|-QE6FTU7`ka@hGid*eo`Y`+&jCL9|f*YM^R=0I>j7HUIzs literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.cas.authentication.CasAssertionAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.cas.authentication.CasAssertionAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..92f6848e4d230927ed23f07c96f1321bf180c7db GIT binary patch literal 1454 zcma)6Ic(HG6#aYTgd8L&kb+d{Pna>6Hbi~z7$3|Fvi8;0vLhCi$lmX4S;UZTGdW;3O#B_(bd3{1T)npbU!>kwCrZA&Nw9IprdXbw+z@p{~iiqbE%-ON~EN0M<4ba zhnpk;Qf;xgVzz@N^;{=%3s{i~d_a{C&>1vZszpoX;H+EpEPLK&IfomzRKL3RAAR=i zc6S_N1|%IEQP5qtE!EKJhyD#0ZPR}auCHGHF%W{r%&V{r$-qZay%9=U2v#BVVCDV3 zd#~C31jMqCl=d}_2f(MCq<27zcjD6VZRIcX+{V;gb6d@q7UfH;#AN+i4ZZ!_g z2DCGU(aMz+8QoQ<_yy*p!##DuKq@0l@kV24gpy!-uHu0rraefV+RC(j<|sI|Mf@9) z_4GQA$@QfO+Yz2&On61whroLc=nMyEu`!+6XlUy|WNQ=xIXGJMRRxc8>BR0EbDzGv zjia5Eg3hR%a0&}6vGbQye}bmVtpKsR4`xnI1z5Q&svKvsfLKi}A78%eyYemp@iAz3 zJZ6nyn|UG&ZK{AC7Cgra{4-}mlY!8no4yGxwYgo5dT4*^LEe4+`KEpJ@aZGiwGG~_ z>E7!LFQ{Y$Lvh^vOn8xm;X&2HsVR7%Q6n=J`Iu681|AH!LPbmdac149M6(7J&oz!BXQ4$N)NR;vJG~Q&rGo2kL zYYK;;AyEvOZO0~ZA1!l4p3ggBt87Ya872TmNIXE=bwd$X~3owg}^u)MQx-hA(S z-}mOrJ1`n4m=kKnilp+xmGg>q_=ZqxR>a+|@^#O0S!A)UZu3z4F4MjUtyvaThgO!w z8V|ud`5+IYbtta!-f0%K9VoFV;)?cZIO^b0Hj`X&VPj(34SdXNud~4SOm4f{kFGfI zkgK>y=dvKuu*X?t>&y;VxMH7at#VgmNQq#(zGT4#xCnXN)5X(Ham{?%Q!xi_tIh7$ zX1hXhn>KjK2D_1NuEYxFcVuQ8E7ew{6?64HsQKxOCoYEbUO5jVO_<2+;~pZWcY$2{ z91%|^Rd%%>*s~%CuuP;5D=lteGQ8m2PGPz+Uj^ug)UV-B2z&0DYib;u;mxrAyomI!KZnx(1~>rwXj z(vJtPf6qQ1fm|I5k^csdC8{O2QJ`PkbaF?csZBUoMs5uH^=$wv4eds_F*0s-2U#38c5H^_Z*4slt8D>005VqzF8HH9c`({ReuUcHoi@vJ^DLZ3IrP;(!Cx_n|fMuK;zkuHeok&UC|Bo%Ob4B(#IR42~+8sBakRu+Ml^gvY|yD(ww|&4rl$c#ObOTlUtxo znfOkyGG*uR!Fmp-g32m2JCrfs=E3)GzxUB*g9JEnMCSwidH$x%hTm4cs6euBmeeyq2XVJC* literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..d3e96900c7f1747ffbe780d473cc174af7e78880 GIT binary patch literal 552 zcmaKpu}%U(5QfJyXw;yIg_brJR>uQiL(rhfB^HpF*pR*50T-6rb9aElf=976F_sp- zhrMs$J2(r495HM)oB99b`~P|Wfr@v~wyq!h-07^}b7F*3>xQvMId+<6u@pWQV|v7l zv=pRm7B_`&qnl`n9W94QjncYfhnT_QMJ9qMfdlDK1-~$!;5ZqGiKrQo^=o(O043EE z*w+g|Popa<+7!6zg@e{c%r(SD>d6WDAKKU3#XM`N!w9w?pt|Bi#i-$LIk-kfJz1iT zNt;^J8pEudpK*5;lF-IK!5p@V9Js}^kSY$+_)qH3uSYq}67A{v^Yr8L_2oMPD00@{ zY>YF-biMU^Ov^=IEg4ExR~?QlVM8g0eu2mR?|GgX{8k0tsK^O+mO5J55mdsRIQCJO MkKXkp2n{>CziCXvjsO4v literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.config.annotation.AlreadyBuiltException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.config.annotation.AlreadyBuiltException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..a38371d3694f7c930f9730f0d58fff78b36c6f81 GIT binary patch literal 11073 zcmeHNYiu1y6&^dO*_&yE}9CJic@0%$b?{{eQCS;)wMJQNxPE$n_gzk=^7Mf@s2uxzmbVF>N`4Kjt

Sk;zVjIreleXpAe#08{Jl?Rq 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.authority.SimpleGrantedAuthority.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.authority.SimpleGrantedAuthority.serialized new file mode 100644 index 0000000000000000000000000000000000000000..e127f24499a37c695213fbe6f5f70f08cde69bc3 GIT binary patch literal 125 zcmZ4UmVvdnh`}kpC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflIlm}XFR`>FBOlBS z&de>yNp&wu%qvMvafAsofB{nu69c0U14~hUPHG8*kWW@(S)zVUVqUs_a0$qC>xzOB N26mT#zyQ~9R{$woDro=! literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.context.SecurityContextImpl.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.context.SecurityContextImpl.serialized new file mode 100644 index 0000000000000000000000000000000000000000..b3a0dd59fc98a4603a814b617355af144704130e GIT binary patch literal 153 zcmZ4UmVvdnh`~0$C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflIlm|sNaU5IR+Q)k zLq(k-e9zp190o98%3)$)^kLvjEG@}M%`3@FPAtg;s$nql0UEB4X1G4gaD9;B`i>}i ItSbr%0Ite6y8r+H literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.context.TransientSecurityContext.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.context.TransientSecurityContext.serialized new file mode 100644 index 0000000000000000000000000000000000000000..5a4ccd07b4d0d97d12c13023f0eb3ea4b04079f6 GIT binary patch literal 1294 zcmb7E&ubGw6n^E==zAkwgOa=;@v zYuzw__(=z8NO_QFJ$ncH37=Vkyu z)|oM=Mr#tVt~y*MEYlv$_?owfB@2v&bKVZS$J~I)wp-oW;l3?-FiraUB4oi`*n=rl zsd5@d zYjMj&UycPtdf+E7gi zr@i)nxTMXcW!V3G`{(5tH02YkTl1qP=-#j688K;F@UBRfyM7n8F9=Sx9_HT$>CB;_c5Je0lz6zd?TmIzNLai^<8l z<{YcOzgmCs^6<&63|5g=H%_nBJY9>}s1?->nPyh$L_dTbWPvyoBMbQlQmOFaXjBVX Gn(_}s!NceP literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.session.AbstractSessionEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.session.AbstractSessionEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..13d48179c5d1f76d4822c689d81a26882fc06d01 GIT binary patch literal 198 zcmZ4UmVvdnh`}zuC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflIlm|s$SW?+%+J$v zOe!uZN=z;ZhVWg>Qu9g{bozcjVqL(=#K2Hd#Gr%Cu;l!_lGKV4J;#EAoXq6J5};9F ztw}Af<2&5XK4fBG^kU#F$;?eHE=kNS02(8jl~|UjS6Y&pqX$;$pOlrFT;kKq=_ip} Ry8>iY0Rsc$v_&Bf+5o@|N#Xzi literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.session.ReactiveSessionInformation.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.session.ReactiveSessionInformation.serialized new file mode 100644 index 0000000000000000000000000000000000000000..384fdd554571827283d481622a98093aa67c8f58 GIT binary patch literal 867 zcmb7C&ubGw6n;q~v1qGR6h!nO^(Gq-ieOJ|jT#8HWE+Ht%5?T6PCGlZ&b+i6M9D$$ zC@2-df5DSSj~?~pAK;(Rvsd3FjT=wFz3jf-Z@%~a*w06B!aKNQT`%&PQ>NE-BE#!*~*gR+}&%wo8=>O>l0E(8Ba;n>)KGM}T7mjpScIOhNW)o|V|h zLZFTUbrhpJeEjeSaz@{rZM*N$B}hVPAGP_pFj?)u zl91B&4Lz`?KDOI|v&Flnm3x?U;DY2J(2x?^PeZF{#EuRbof3U|W;twyp5V;bexH=C z#TdMw%!Kz`O7a|D!5q&)mf@mCGh642{ahcp59gu{t2O7wwfN<>No}IKDlB8Z;gxqk z-+mglIgZ@HjoD`ku1o7M{PE=X)iRWtP?7`}r=%7zE`!x970lZX!{%Qfx85Hfy?Hbj z`e4M}|IdGU?Kr8yhD|f!1lg%hLd_MfD1@;-n|zY`Ac|F*X}st7U&Q3^ilS_`TX9xT i@+O#XZ8qyq+i|^>6D+ezw6n>Wmr4>?Xk&4s-rm~?6VnQklEupG7QIsPCK?1oxCw1bpUHcuq2!X=H zf`kM@LSkcP;D0b7v9hvtX5~3)xG+U{v7i05=SvNPEh5&G&)YF`@ZfOmoua*n5>$rLA?tNQCjIlmcV(%JhsJgAv_|iB?I=1 zH4Bugd559cPGBYU?Z4hMo`0G##Q&;A9Z7zW)gEzszzfj17kao{}~ zyo&BU!p$Xs(w-~>L~nll`uw#dJThdhBx~TpoHMBix|c2Uo+8MO;Uz3l9mdiW z%cL%CM2qxXt(5N?rN}c1!*6wTZZg@Ep&9XwFTeZ#_S3jiBSRcqTl}Zu+T4Nhw9u z!gY!$n6a~%eCGNnPGxSD*mF#y$o{sd!X~+OXVlDZgq8L|(t6rSTkR5I^|?02zLMHMz7Xv!qflIlm}Xue3O|C?&Nd nF*B!FFBHgDaV#y#$Op+g=jRqA7A2PC7cqbVQw|dYLq!1qM=u~7 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.userdetails.User.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.userdetails.User.serialized new file mode 100644 index 0000000000000000000000000000000000000000..4602e2af21ae90873442ad4f88438894da86f137 GIT binary patch literal 299 zcmYk1F;2rk5JksE5CljhNL*trAvy|B2*?mCjYWaGVLdoYmR++uCZ?g|2yhB|5O?AZ z?2-be8vQ@|^WXb7ta}Fs)-}SnPMPM`;XN&_TL@3GbBdpYw2nmQ$<@TDv=>)lhu=wc z3WEwpD5dQT ?Tji*(VECsMRxYBnFM~sd6>9(gbU+4gG}Q%cW5;tFzAEwzyTu(o zV%D+JSxJ0Uz-Eiyhjul?-pbTynylnlJ^Hbx2?P)D3~ApcNdkdy6mvw(5jpyK$oiVR oyuN*Y9IgTM?Q$o;43AG%Og+QKcyf_no-%Az=jHf%R!z(N2j4|t#{d8T literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.userdetails.UsernameNotFoundException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.core.userdetails.UsernameNotFoundException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..5eb8b1c9b9e742a8114d1fee4a07053a851b2181 GIT binary patch literal 16762 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.ppolicy.PasswordPolicyControl.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.ppolicy.PasswordPolicyControl.serialized new file mode 100644 index 0000000000000000000000000000000000000000..51e783d58cf04c32fe63631115cce63f01a00412 GIT binary patch literal 96 zcmZ4UmVvdnh`~O;C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflCnd2!ub?15Co{QH uFCekF7$}_*0OmU9=am%Y=ct!j>38*Q=3!!BjAGzO2I|U8PRyw&U<3gFp(BI< literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.ppolicy.PasswordPolicyException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.ppolicy.PasswordPolicyException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..cf7079ecb45727245afc3702b8eba9df05822c7b GIT binary patch literal 11328 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl.serialized new file mode 100644 index 0000000000000000000000000000000000000000..0726de84244e176e428f04798b96293347358aa5 GIT binary patch literal 506 zcmbV|u}T9$5QZnG(SX4s2-d0W_N)~V6OSMSFCWZ-osCt% zHk~ivL-+u`fR&A1+=PhHGSkic@bUdK`hdJfs7Z{7R>(!vLz=K_iC09kAVqElBo3(} zO39c91Ja;c+c<1Y*Cx|S3eBog7?kmA_38QU?Qze6 zR$)Df%*zxnumLzLJu)TG*gSGSh#KTG$=(w$hkGfVfc zrT2^3%@Fn%Z98kZmF~Fr4=+Urau-kt?2mXr<4oml$Kx>olvAAek3KiQRtoKR2Ov}S zytM&EcgTd zgFXMj7qHxEIH(#ig^suS=ZYq04wVYj zKLV8_L9;^TT^Fq-y(Mf<+Nvx=&lE{WR>-+`3g}OJTz?MuCj|Q#Tl@53a`&cbFVG2& zBA$aEIP0x4DoDIQ$M=}SqniSH89@YWQ%r3?^^h=pm5{iPPL~2l#NkWI&wXRTIw=Q*s+Q!W7PK>}9!RBCQTK3F^vhi38 z<|7jr+e6o|LTBol7SLb1Lx@^EBkYcRSoxD+X6s`}(*wPGIUjijB-C|RGZbot3u_k6 z%mK92(B_7wDb%#)foC`u0YHIoj39In_6M3BLfLFET>x%4-5!v)yJIUfZEtM$01TZ; zAHFqr0(~^*Q^WG0qUQ}w$n>VR?Q5ffKZ2f2doa{=Sk>+R97-3|W$zBgz} literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.userdetails.LdapAuthority.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.userdetails.LdapAuthority.serialized new file mode 100644 index 0000000000000000000000000000000000000000..55d14042f2c3d4c17d85c2c718703974bc31c168 GIT binary patch literal 265 zcmYL^Jx&8L5QQhZ3rc8EXt{$|p-d|N3Z$iBg(A@ntZ^KSZN}qdX*dE`phbcXi3@NG z?!X6R WCk&hBpzj%mIgd?I8LG?n^x_w=^;F3K literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.userdetails.LdapUserDetailsImpl.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.userdetails.LdapUserDetailsImpl.serialized new file mode 100644 index 0000000000000000000000000000000000000000..55dda6329e78274869028731f821a44a234b323b GIT binary patch literal 401 zcmYjNJ5Izf5S@hO-|+9~pv@kDY9UBOva*B-5b1_wHkftnU^{G<0*PZ#a00jo1s(U` z4mi67Ofj1G-qVznFY~fEPi}@F#N5TugdhZY?a`#K~E5ZecY`hF58L9Vf0%laNKjEKGj^ D2)BdN literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.userdetails.Person.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.ldap.userdetails.Person.serialized new file mode 100644 index 0000000000000000000000000000000000000000..7b55c61260f06aae38d4a480c4b5cc3db64b340f GIT binary patch literal 656 zcmb7>yK>V&6o!u#h1j7Wgv$&w@CtH6Mu^yG}Am%N9Fl&iUtDzW>K>*i{nV3R!xpmXw!gl2mvl%9xF)g zH40^cnoy>^6O>AD*gl6jup5Gta}5XKM{-H}ji#(0Ql;O8@VG#gOIm9xjNtG_zz8q< z3vI_4Nw1`r$RjiM6MO;3^iupbdZLkGeJ(ggO?8TLn{Fle|F>^w4oJ=ZqnrJMs%H1g z?{DBK$#co|_*0jO_o(YND{>0KSY)GTeg+WYFX?Xbn-cT@xxm6iHPH~5$TN9;>H^nWUt~G_< zfotnd2RZ<+{{p=FV_bb^-SDg%o-ZZ2wwAPCf4%wklYHNSP5`c=pRuhii!0aut%mL> zOO9vB@mfPK4*g|38O}EvcKrE9*|Cp|%-yP84SV5iIXj7uOf(p0(JCFAXyiwe%`7od rlmyvik(sW&52NXF9gkP326r_$vem_GK1|o9_x?1PXN&3kbQu2wBT&sC literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.ClientAuthorizationException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.ClientAuthorizationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..0edda9a5d7453aa85f5f1f2f9191fe01b693b047 GIT binary patch literal 16940 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized new file mode 100644 index 0000000000000000000000000000000000000000..9c6c667a11a6bdedb2cc73eb17a2ed12c1474946 GIT binary patch literal 3455 zcmcIm-D@0G6u;RuNw@hlZ5pg5lp4WmGgC#RBn@%%p^cqtX%b7U{*eF(mYdd|JOnb{BO2yGq$d*|Ns z@jJisJLi)>NP8$q))xgM3Sr(r(Y?m`n8ZTz?7yk1F zf1Noq>X@j%l$#>=S)_~FHVeZ=e-$2(GZt7k8?2i(>!!AD+P+|B)pqoH!Xg9O??qPN zp%hedA8cC5Y^u;q5qoOtaO2j%M-QfM^MJZ9K=zUhSXbyeHC^fz%ms|x-M6iQ}8t4%)-+k3<+@8z>-YJW8gti~BP?A0!cUf}RTwoG{ z&PXi6;${F@J^Ozn&PM)5Rn}-{q9Vwd#DQm5uxPf_l~qO}s-z)4RtY$unw63q($Py^ zV&=RTO6p+-2Y48knUj@ky)4gi#s|NSPi^}saQx%t|kBPr5mkv3bBBeBv&*ECN?T3eng z1#%O@7~B)XpQUd;ieVtFW)oMZso$b9M^%CV+t*`oOVTmTs9*wIJ)g*x7LIru8qIAO zIfNn~LCHaVQT_g$04it>XOnZdCQPz*cTc0d2TQEg#Xoud~nUo+p(9tg^3n!7#jn1fRz!+s5g6s=~zs?;d zW|^ei1%I#HO@9~at%JiZ!5nCJTP_J+g`qdji+#ANl7@FW6{)8ZF%7k{b=@O+HS>9Z z#@V)P`wjzc&m{J!acxC}J9#~%YrPDU5D2imLPHKZkkO}O9Jtt}4Ik4(si*oy--iG% zCkil8^EFVT*nZ1A^}%0@piVi`C)ipUiU!s6^XJ%RB_QBDcNd*E@FW7Q(<1$KwTWmm zX!~BC7s`4gfbOLZm9%~Yo~HqDE~5sBh@NJyY^*{PLle7E7bvp^6EZi! z^a5yZU;FrPxz{HX5_|e&j zBqv+ls>@1kAj_&Ekl5?kq{BLPDiljNaViDNm8g& z<`#$-TN3WJxdpo(tV-LpTBDYlTlEsZ4P6>P0j*O@SwB>GC|Y$a?yWNCEDej$tUtKl z=XQU34E7~lweF8@Rlnbuy}JAU_0OM5tCbHHKhi(-jLNbNG*v)G;}~!Se(dl1;BF!@ zih5Ow@9MHMz7Xv!qflKe4nV!$>bVCo?s# zM9<$5!~+reMVVEpDb8SF&lCnQV9H@)VDe!QfhZ12P0uVYDM~EK%+K>oDPa)u$x19s z)Xz!GOV+HmAJd}Fy zsvuMl@hFIR6~&X{!GkCDpa}j4dhj3~6!E=H;-+bd1~R)lGw;3cecw0p_$zGlCFESW zr1_q-oux&|O1$FA70u^nS=y?qxvZ>;Bbq5}?kJ5u@M)V&*{-8y)3iuKG&$$4a0kL) zT*M)6zz{{b(!R#6@i5JKzGVAKhB-2pg5Ab4yUKLIoFzS{2*{`borK7=Jth>KG!UcS zf>Cc^)N2@Z)0JE=`&{ZZTzPyQ4J1srZLp=Kbb*kWYKx}cKRUeXOk2HiNX)~|rZ{dH z&|Pm*+1yt!Q0u6yY@v_4LU5D#!mc_ju9z)wOLNoN!ZPj{4ZIlYafry!C{&9={eyX@ zJV(f}y z>btjfe0uq6;PTr9#HXN3xXhYD_qj3yovJ9gUvz~Pc&AQ>OvEZX@EON{Ou-In&%urq zBM!gTU*FLUX!k9u&AIb zH=aE=xga^Se5$w)LDZ%yC?F4Lt_M-fv_(}0xwYg=wg|uot%&es9!BRB`>*( zr!5Pq3hN`lP9NX#xMeIV9t%7L+iUw&(;8YxHr+$VYztAIYKwA{V<-3`D+?7}Hxamv zekCBit(Qczg>zh?g;ONwU$>s&H7BFnC_WXAyEj{b7W-)!2mpKd4_jlkwF9@`-nzGz zM>B;Qbzf$o#fSOz&qwf9fXyDp_2&~e*6%!fdLn`AUZn>~NYzm$2v#2rgEZ_9 HOZEQ&L^vrA literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..2fc9de7065d8302f7d79b10750374640d85538fa GIT binary patch literal 5546 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 W1dlzt_R5BDFTMp=1c~0BuKxg;)w8Jp literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.event.OAuth2AuthorizedClientRefreshedEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.event.OAuth2AuthorizedClientRefreshedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..9588433f2b137e0f14b3a31f403174e9d5d2a4e4 GIT binary patch literal 3632 zcmcImO=w(I6uy}>X{Py4n`o;IQX^<---|_DBn@%$W1ITgN|Pxx{)9X4-I=+`oA+Mt zyOT@|kt|w}LJ@=%#gz-83s)joTGyf!!3v@aU3Arjh;&ihNb%fz=jG1NqduX{Vj%b3 zdw#z2o$s74{zPpdLs_2{j4)u-E94n2ku{&K7$LFCjPiBE$7NnTX4ozz9yiFU_&u8u zXX3|a^gZHC%a7+so{_LfoLO<{?zck^`wy?~N}(1D^$8=jcR8B2z^&Qf){NYm%B`u~ zn(+?DD-0cp3*PoUPAc5U1c6I!%&G6mm~xl?So+|^4|gG^Qz*@8iG&=N0x&bUj90Ny z=F~Oh&9k{>Vsq3Dy_B#KDq^2Y#Lzq z4Df}c0ZYJVy4Wk2^IQ-KE_E@*hiY0A$wJU+Rp}(3k5>YKeJl~|U?C!?2IlAQdyM-N zdB8hmq4v=B11LS}RdI{5oAZQ2&}lA;u&^FLRCIXc(4>li?T9`;Z{oBQrXW-7*m;9TLfI zNq6zmm?v!eol7^LIdt;NErPJ3GPln?y&z1td4q*&FUY6yNNadoP_99L#tC6K$e)f%`t zYM&yQ5eBYKC32;twzdt8<~Gz0_gH8s(pTBLeHgFaRD z^fUoMcQ~3&?m*=bXGYLgIhk`LNrATNf1xkclP+~KPi#gcxjQV>6PCRUE~&C)`wjtaPbc=MaE&9vqrC1_wO)cb3j|oo;gEt3 zq}AzB3S8_|hM!YIsk{0`-G=}#BnmK5^EFVT*mlPp{p6noP^U%I%gDtt6b-8B=ieaf zusMM9#mDHpfhQWY-8Gw*tc|A4AnZhWS}3cH0J;}DnB)2pcp3-5xwIT0Og~L8udP56 z=!14%q~XY@ARBn>kOM}4?JI|b&3So$)^h@%dN8R%d_YR5#z(QwRZv;zbD!rAj29f`YDmz5k%OR^%6*vgY5T@&n@g|N}?6Yw? z5IbO;IsiEp^;5{E@-)?eLU7Po&t4EvH;wfgm~>i3&7m$$Fo`1+Mrx$?o{hx#U8 VlUcTarZUKAOav_Uyq#S?_!m_R4ru@Y literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.oidc.authentication.event.OidcUserRefreshedEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.oidc.authentication.event.OidcUserRefreshedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..22588d03ef900718e19622e3cdc639d6a037312a GIT binary patch literal 3770 zcmbVOU2GIp6h6CK*cK>kY4{64ffPb4oe2ehpe3;MkA;g!GstR6BDCB6EQycV0=ImH7X{&_@X@cU^I%vbMEYPW=mbRHtE^9 zKj-Iu=R4=#tG8j1=farno=`l;HLMepE-UdV+dZXtTr0bVm{x377R63QGYxJD#Wr+J zp&1+)8WVu(!5}Ao{=(#jchyL^V_=YqqI}!)M?eBz+gQ&j*NRTY~#S z$vTc%Ik`OH#D!nRUmW=IRuU42ph*}d?g>_M(9Ein>@-u#!Y~!NvoLXzYa;(r)8N|D z*(*r^-vLNmdh4T)zridN=5rcCF&Ar4-F>H2*||T(4Hl&9EN;+Vps zQjlZcN#GxzXG@faC~MW^KzrvmymI5^H_nexEs}mSYPLk0$6U^F*UigEU!A%A!dQwb z7I*Y79IG}e3z?`MyW}vJ2|I=#nhm%>F|_&umOpFRwsST-s(=PMCAzN7l(1D3VW`@|fdr|uPPd{7z^oAk6?zK2^wv$osM5j`n=STF*5A>0T>Rm1RjrY~O|g=p@n!@!Ten9^{K72bXWh}+ z0qDxFroQAL-ta7JLNtNBa}^mHjPsoaauJcH->p(^yO&P1*iK?Qi4GD9iJc^Nk$9NI zBP1Rr@feBSB)Uj+li2G6GN6#_?UC0JO|>KRyXyu*LLciyKMSr671v*twTDz=MoAu^ z4dLys+iLif2@sz1m_lTmkcW7~dDtbOHMpRNjcU%qm!BmsDM?v9E9giEu?-1&kv-1L za)>T1NFeqKXf*T=xuGWHi%BJbjlLo%qclW7&rJxD-Gx1MBQT3AHB@Tr5wq}lQ3dRL zJW0v6L$8}8g&GAIaH3LqEdrJd_Y{uyjk}x$od~Cv22)e`*5UF_mfAV8Gbr)dSQDjm zwZjg%d99SyHJ+f2ozlZj`uuOsO6911U~B1awWMPAoF(imv{bXRkgxqxZy~jHoYd|U zCv6!eAxSdk1AX_AXqW!_XE%(|*P)t0L~N6rCEKL&D4BEE77MWgz>#@8)JY8ra7e1R O(C0A;w{*>Dtp5vq2bjbF literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..44ddad76c51b67db06255b07ef427af7441e534c GIT binary patch literal 895 zcma)5ziSjh6rS6VyIf2#QSk>6i>QT=%t90kPta2g39O!YMl>imw>!C=-0aL|=54ZR zw9!8xML=w<6blQrv@v2OSZGSIun{W@3mXOX&7K$qiwx}U{CeN}zVE$#^qKTpLuRyT zQ=1y0+H-~_d`X)QwLHv>fMu##2Jr+9rQiyv7Ewsu4lYE|KSh#S~*pCS4tdcsi3lJ zBRmkQ!=ou?V+=U5cJAtsT>Fjzvt$bkLvC%uMBJdi!y7TbZ}(Q-y?D9v`r97jO_F}8 zSu}~RYBNo$5Tk#wmQkLbI9Yf*u+)d&A$-a}%6t(EZ&AqT2I}8Jn&_J3pcRt3x4*iw zaI>@G5icO6&c6mXTJtC#OHx{N@`m#i$V3c~9`k)0#ynw`>Xa)HQN_V8^7<_ncEXq` z#pQUn>yXL5|1&w0q_B)Et{mNQd-naOH&_yvqlabDCg?Xgj9sYTIkfiVc~OK4mpnn~ sf4Ek=Kg76*5L2+?pwCS$c6qsIGlR={5J0@*l6CK@#mZpGr=y(w0$8;%0ssI2 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation.serialized new file mode 100644 index 0000000000000000000000000000000000000000..9fef9750d708d6383e708f6542ef442eadcce889 GIT binary patch literal 2430 zcmbVOO>7%g5FXn}9VabKp-l)8(3S>T)b0uh2a=-V#4WXAViY$>L_m1nea>$3?A^Y7 zOEw%(kl+duQhMQrgoIF~N?bs_P}Eb!sV5KxP6%<~zyTo@sF-;hd+jt>hDaIj`{rK>g!kA=#4(y)Zf8_Mup+rqOTaJ`06 z#ziH5E;eeOtTTn%zbxI#e)`ko?KGrHFhtrO8ghA{V1H?qy}`^-xx##b`O}ev(_Nig zp5!fece?`pmlkR0TY@Z)d{=TCoeCDH1%5F^p9+TCoEDhk(0 znXXAT5vE)UT zW78(VXp(7@%oSs0Oml!dQRx(%{vU=YF4Cqtp6NNoj_mS8F0zx9GE;&!Fep4`pJSHtWD#=8kvwobVMqR%=QIp^i9Ilk|5*i@Agn6Lq9ktOnuT_6 zSs^l;4}Ng-{SP-6)5u8)CzG)L4T)rbYz8*Je(jHkVn8efBW-<@T*F?#Xm$k&{eLUI zS=##HaWq2ycff+@R}1fMUHanl$+&0sQr6oTq!i@hnRCjuBTD;CU%{SuOF4;bn`4T5s(+^BH5_3ArU$B&->3|(W> z?!!0%oUy6nPu(~&OI}^3GzA}Rot?a2pGBbeN3|SZZx20#UJ5<8L(=ihR;Qkzk-72Z z_+R!D@7W_!-FIakr*NI0Evf5942vu#8DEDkT^zSg2ZDaj*~bM0WzRmEX*!1 zR?aTa)#*pB1+jobk(=@<=u2)^!3Ygtz;9s(pm#tAUc(+%dv>PIn-i zi{DcLPAj-C?kQrUS!@J~HE5U(Xv*4oMJY|2kN~~2AV_uzQ~w=T8tn9fNb<)|f(~GJ<4H!3zdqt3g_;Byb3zBx{f(+W z8(MTVDsJ5u{CSD!a(frLV^Dq46HRax3|3~#FP>eNoY@-GXB7;^TkF`DJZgpYG(Alr c3tQg{9gI)uOlYX!IO|`w0$IafU91TH0a@e)SpWb4 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized new file mode 100644 index 0000000000000000000000000000000000000000..5db839c1aba30abe2da240a9fea886e0f82bb676 GIT binary patch literal 1845 zcma)6zfTlF7@a!<5kY>4U^GZ9u+ZR+F~$O75Kf}R#Tb4RVB%%iox2-uZ)cgAg*yp` zhT6mg6XU;Nukl~7kxC1#t*mV<{ATBtU0{QI-F-Xrz4yKEz4`DPbx460xTsn(5X`Su z1ZmJMF6x$~PAHgaS)7EbcE@r&Mtx-oT4hoRq8RtBQdC<=mBwa5=DAd$=vVZLXuw9p zx&s&NWpwM2Aii2^1yrGF8<6oaWE>-7gNz+6D4x!2F=?azBh8nAGP+=|lTCs>;#cva z(qqB7D`o)Q2j*Z-#ATiWF;}74{6x&mS#1F1tO1#GH_$6ow9z^9|0#&tuJoA7B6S2+ z?OJbKQwq3Bg`Vf56RD5W8^NWM8!@FtYMKMdA&2`FRt?P(K?33uMHK2YvW1Fyc%B4s zvWrO>QURj2nRcyj)uB^T=oZvvf;y>EZ<^4d5}nfF0fpJpD9}oarg)wDNzai`QZeUO zc$UZtP303OHqG&617LRykYEn!l3vra0>i9@iXQH)89+p)azz9%Elv8Q*ONRHY}Bzy zywDh;8Gv0$`byd1E=1!-O{qXmU`(5&5qQ*ccmsR9%6t z9)-Dguf92W{eE{@6BcOv|96t9FVCReFE9R#f?75zzz&B~nNN;QMxMWvbhepar|x4S zYk>dV`?k9O^CmRW_yftQ_V>>Z_x9g@`gr$@7IdozjMYE&AhK5hOd}|9Cv@7H3XNx} udc@?8L!~T74+Q9QKbMW-6dq6hX%6qK0WB&N8i^^9fLZD6Y03%}nfnjL-j5Uj literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.registration.ClientRegistration$ClientSettings.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.registration.ClientRegistration$ClientSettings.serialized new file mode 100644 index 0000000000000000000000000000000000000000..14e74db3b465c6e822cae112c123e8c8e4eefa45 GIT binary patch literal 129 zcmZ4UmVvdnh#@k+C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflKe4nV!$>bVCo?s# zM6W0{J+ru^D6u3nKTppYEEa?$qyiBNPAw?`npT{_dQsnC=ARHI2F54`{-V^v(#)dN PfTH~TH1E{PiULLeyTLIu literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.registration.ClientRegistration.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.client.registration.ClientRegistration.serialized new file mode 100644 index 0000000000000000000000000000000000000000..9eb1e5f751c68e768a4ea4bc96accbd37a45678a GIT binary patch literal 2471 zcmcIlzi$*r6n;L(KOxwF!3qjiic1j$JAk4TpvW<{MGQ+6VXnYISmWKfyYcPqEVFOz zb1X-0s3S#kBt(rwkv@ojfd(p4Bz3A3DWWtH%9|Z8dv{);EzFj0XWsYC_rCYuFMq*M ztl@^z4JVGY3>$UL+hSAcmJ0XqYaZgAtwSZoCQ2=uw^t_(f@J#{~HqR0sH`iE*!OyW#MUo`>|tL z43VzI2;Y^Xm#WLU6=(AY6tCLQvardC)I_A!hV+GA5Xfafc=rwy@7vz&1b9RXUuxlD zP0Q@b-K>}V{pfJ)sYno^2`grTGGN5~Y9}Ha&X%$vnZhYHMee-;7lt-?kl3PhBB~zm z_R*@Rd_jowr4g;jMJ{<4>PcJpRdP1DVReni(nA=t(F>AXb;KtA#5f4y!~H+@JQ2KG zieRbE?DI);=oP#8=)XG^7+8S`EgmFPK`O1P-xOP&2nc-T5W)LQlZ!Uu!emcvau1#I zR9Kgd#10#3ZSHgAz2>>aBQoHajN?S;{OZ{HW{Z542ZpgJ+M2zAqjEN3!M}crW)njpz6WH_ z{rrIoQ%2r{J88C@53;4#pMcC}O?=7dG33Hv@^;#uGOjZtWDaRFttBYlZlttGUzU<0 zo2>oF`Qr}G)#0Y#v}p{bX@rln67fhn-R3j=5GrQkv27ZIqNal#w#`$s?PRq!Q<-h| zZjK%5_wEQy&2pxt_TN4G)2kmJ@79d@G@LJXYPZqz2KeY2?7q1F?~L_34eiri8!JC% z7uR&2*_B~S_+Rt5N%+47uUGc|I7dkg{_*_O{{QC-5BGk4_Uq?^ra3l`lFpN}U!`gf N(3a4o>=h$i=YInhbe{kK literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.AuthenticationMethod.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.AuthenticationMethod.serialized new file mode 100644 index 0000000000000000000000000000000000000000..4a1d8a385dbf79b62e3130dbc3f2d1770f6c6ec7 GIT binary patch literal 123 zcmZ4UmVvdnh`~0$C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflKe4nV!$>bVzbIAD z5y(uMHMz7Xv!qflKe4nV!$>bVzbIAD z5y;Fh%B)H($;{7lFG|cS38^edWdH-F93}=v9|qR4#GKO95(Xikti-ZJ{hY+Sbp7BG RklEH11tkou?yi1e?f_V!D6#+m literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.ClientAuthenticationMethod.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.ClientAuthenticationMethod.serialized new file mode 100644 index 0000000000000000000000000000000000000000..f440703d99daea227994c12184af4d602a00dfb0 GIT binary patch literal 126 zcmZ4UmVvdnh`~9(C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflKe4nV!$>bVzbIAD zIVUqUuf!230HiXL6H7Al^L$fFGV)UxzO=uHA6n@F3f3d}CMNm;h)RS(c@uylr+iJBCZEID~QtM=Q(oCG}tTS($ErNLP z;LVeQqBjK(dhzH%5Rc+P!GoR!=|RDRDB|CnO=3jQh0IRgoA=)LzW2@Ee-CX&LqX{| z*MwRGb0y8Hyr%Ry*KjY=0vBAxA}sg2p3>Ysz)LKWI6h=ecHsdEk0JLbDB2Stllw=% z+`j4FSWJUcfGk7Qq8K4J2ulkUHqUYq3Yj~?!odP8v8feR5!S!*5C4dgl6xprK*urx zfkIeb_e&Pc<)$cYFgF;71b>rF>)Nz#E6i*>5mc2gN`e(7r*a?f-ur={I)>AbDnP1w z9xg&E318L*-j<$8YdGd7S*%uC^MMomNhqJCH*+uw;$}&UF)Q^ zmwsIc4vTz3w7QjYNNcuWHYTrvF!=h;-p9|cw7(=6(~x2aebaxmHD0Vx*VI;|n-16Z z>o)p*@bcul#kCg>r2*oQN~RDtmk~p=CzlJgLJH=-%VSmzC3gdkIfAPq?Gb&BiHZm- zPVX4cN8!ZCz`&#)s>Iv2`Gg9bpjaZ{Jqd6WRqjr4eQoCQqcs~2KkT;EYCTjG+Fwn6 zx4lA)BppwM+*l^FkzISp?jU3|XFj=WG+3(J;VT)IMLZ8j z&^FIx#N&|W;Lt1KqOZ;r9KUhzXoIOPPYSvzvw~>pmHqjFO1?kuo2~tMQQ3~r84<3! z#xoUP@t6v4X-Vi`qHft&4a22pXFjj1YumH^q#J0mq@*3w|G(kM)jp(IK;p5N+TO^8 z!P4$;aCN43s;M=HP!0e0OOfRHJur7lSQY-A*HQwImI>C3CQch;LdZ^yj87b#wem@r V%pokPn-VECZ9R2cLsm9=*)IqemVN*L literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2AccessToken$TokenType.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2AccessToken$TokenType.serialized new file mode 100644 index 0000000000000000000000000000000000000000..2af983f2fb1768ba2707b2f445d5b1daac5a2b7a GIT binary patch literal 126 zcmZ4UmVvdnh`}YlC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflKe4nV!$>bVzbIAD z-x0)iOioTME)L1hPR&yRlOdG_sSIGil*7cp=)=HTmY7qTTEZaYla*MOsGpOVm#!aN S0y`tEMx-p*#`t*(V~ zh1P3%;)>#&9`4rHX2?j-C!osGH%qh zXyK~T%gkVyYDsHMiIkczS*SF!55C*`VTi`esb!2WPVckJH}%K^FMvBHR0$e_2f-Jy zB6vzN;x(jd0R7}{&#fg$FcVlk{)Zm#=suT57Y%6Ibtx7jv}L!S?woHQwC63|HZ<}7H(l1~B(%>y zUp@1n6hJ8?82)XJ-D^d(p)Pq=n5I=encmvn`|-WflgG0m;o;kNE+dr4{xkPCG8<$1 d)KkOu`I`U$ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2AuthenticationException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2AuthenticationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..8d98b95901250dcbff7f083ec8b3b7f892c1f407 GIT binary patch literal 16972 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 CmvlJ* literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2AuthorizationException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2AuthorizationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..3cadcc338998cf3caf13fbc3281c89e309fd6357 GIT binary patch literal 16821 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2DeviceCode.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2DeviceCode.serialized new file mode 100644 index 0000000000000000000000000000000000000000..8a382b26102dc2696aa13fd0149bda9c89f92276 GIT binary patch literal 313 zcmZ4UmVvdnh`}MHMz7Xv!qflKe4nV!$>bVzbIAD z-x0)iNiEAvPIb;tN&Uk3^UBoshr*Z`7%GYwY=}0;F{!wuC^5MNVqQpoc4{627%=59 zF);fuaHduiWEQ0sJC>9%2>WCumL=+!Wag&od*&6FB<7V^`!H~178jSMrZ|=qGSo3J z`7m&mfD8#s%qdMRVGx37$w|yh*AFfM`OCVZ0O(O(kV$$Vlk|d9i>Ai5?2=aU*u(>L jd_g%c&<_j@>D|xjjroEiDvCkovLZz5z@jA#tYCWp>J4f) literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2Error.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2Error.serialized new file mode 100644 index 0000000000000000000000000000000000000000..3b3bc1a11b8383ca2dcd8b2d9f1686ef7116f94f GIT binary patch literal 159 zcmZ4UmVvdnh`}VkC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflKe4nV!$>bVzbIAD z-x0)iEh@?{VgLiC93}>49|rD})Z*l#%z~24{JatdA)l`6;P|40Q~QJ`Bu2MHMz7Xv!qflKe4nV!$>bVzbIAD z-x0(PN=++DEzStZ&rZ!d_Eh|ZY!m|+Fy$~Y zF#9lYrdAYW0&Q_DDPa)y$x19s)Gx`*P1X0zD=taQE3x)r;K(d4E=^5wEGcBDV_@=O z;3@&RCoD0iG_{052%;q?F)v*|xCG=g>xu%PS9w7u>48kr3r;PX8r!l5Um4?mN4)XA$cDF+)8bB literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2UserCode.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.OAuth2UserCode.serialized new file mode 100644 index 0000000000000000000000000000000000000000..a3fd001c78683ca627a4eb914cc5af5ee59ab26a GIT binary patch literal 311 zcmZ4UmVvdnh`~I+C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qflKe4nV!$>bVzbIAD z-x0(PElw?R&QD1_X}tByAFrh>ObiSaMGQ7Xo8p*MTvC*nTmmsIBtJVfj{yvra+nyH zeHb`XD+)4;Qi~l+N*IKFvJ%S@^-D5yQ}sRbic1pnO00btI5LZiOH)%EO9~n47?^w* zxJp2VgeB&brj{@WLA2x~=B4Wgmw^e|@Pa2ARu>5Um4?mN2k_?EwIe>umY} literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange.serialized new file mode 100644 index 0000000000000000000000000000000000000000..cd379531f3a4913e7889a67fb19b983848cc4a3f GIT binary patch literal 2008 zcmb_dO-~d-5UpKU`O=6+Aez7li6%0W7-OPjgJ3{O22hcUF|N%_v)eK|J#^Q?;=%9_ zOuTwDCLTQc127&rarHlV@IXA67^~-Fhb1G~ME20TJKa_9^{ZDkpMO9}D_E4OYUxmk zpt`PDoo`FEVQKC*l)xQJvIf>}TCP;w;(-@R5umj^V;-7CD)E}3kilHjt+AlWA^H^k zA`~6CWP)2qp76~E*NCv>5Ttz!(oP}m7}7S7b_!|dKqmR792h!=qeB^Jj<9@MKykvU zW=MLOod2&^LOzqL-^k^8!idTIN6eH0xzY&domXs&*$ouFy~skkZ$Cp+Vy%H(Z^9cW zIWXL&zyrkse7X~o1T&pW&{oe@3W6JHOP(-aC40cdn3_+Ow^xW#P^II8ZO7@H;n?Fq zuj>mQ;DVR@A5h#Aio2K{IjeXqbxeMxbMhw}B}+>D&S+PLWW+XMS2)sENh3oZ@r!DG z)hKTn<*obD_lYZ2M~|-tb?J$9!Pb0E75Vw`(;qzSnJq%WfkHhhNfbVkg*zhH;NC-~ zYZP#6Z|V7w8i{JDls{z%`6pHdSu!UHN;TgwQb&H_0Z=ue(JU_t0IVpgPjKP z>C`Y4B)gYQ_(W_ox=uO%Mc0^!>ROK0iPUx#nsDVjq*nH96nWe}suiU~xSkiA%Mzil zMFrtbUR+0*REx(Q*COqRylMU9NSmEhoo*v2p?1&*)IhtV?+)I+-(4klk&$m?M*dgt zWX~_p!tVZyUt@6#xd_8YhF*3op1@=Indj;u?huWkLpA!PYyX;|5n|HQbcxe>(WTO5 uvUNKU>6DDM5?4wpgxj4DulVIQXX0oG<;=ylxpcf~CQXq-gMP&<<9`7Te8Uz1 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.serialized new file mode 100644 index 0000000000000000000000000000000000000000..029e2eb8cea1165d244555675bb26a8387cd2f38 GIT binary patch literal 1469 zcma)6J#P~+77YRAf)K$yyNRiDzS?ivlmX=* zAh9wcBnC!)KoMi7uKWiEhKd0R@%k>8YpMe!gLCKi!;hc$dG7NMQnrTp+B7{I85uS= z3~P%WZ8kkCf{u}}>uJ`3)^#t?MtCCRk(MEND+_MZE!xP}45SX9h^>yWK;mCf70HB8 z&N0rV3o`YHxz->IrkwU)v28{>AQgSUqB);TFaRUhJ0R>9c|!_5sk&na&F>i&!qeSI z09kNw=`px8g-a8-)WM}g_y#TTxapzhedKF+U2-;ordZgds|YD1X>TlM8Zi4>Q}gwl*Ju>iDL zCgadT!dhDKZZv;8(bCQs{!h8*E{WAqiP!Odyb#BW7%m_IMl_`b6DzVI`LUgAl4B1; z=0{e1zJweYLY&BvpYk?Sos?o!(2!Uff#xXM4=sSm-l9}=Svyh!?bN19H)WUzmZn8w zJ{3#@Q0{eCUQ1+#*QqB@c#T>-*Lu9xQrR`ovGu%@2Hi7za6yl9g)MWcwEu tNpJtfui3;x0g&pEk6xBG7u#w!zw_O81iJ|1{GploQrf@lU_{XO^j{Y_0mJ|R literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse.serialized new file mode 100644 index 0000000000000000000000000000000000000000..00a961b37a56c0491788ba5db40e9db805c51052 GIT binary patch literal 462 zcma)&&rSj{5Qj(pBoJ@D2W?`EiFzUg4A})m(crP!9hd6vwoV5{4?dBv<~!If3m8xK z(4>=ezTfxz`i8b6m>9~0v!u)HkyL?ihMtAPWJQ|ag;6VB9*e{f3M^A=bjjkXABN#F zq(2mEQ{JGnrgR9^Y2*!P#n4JjiVR2blX_J$S7jz|Ie;!=Xd{s!hSS*d^5upv z17B{i!|=~9I3!FpVZwK$U*HoC{7%P;v3jhn=Z5UNguM{P`=0#U_ZM%k)%0Y)Cn0Tv zcTMhAHUm$n2Tfn!E=Xt8zl?^HXw1+Z4dTW9Y%!TJbgswK>HHy@Gn_4%tviP_$;5DNC>!Got3{{pYxdhzDLLr;SECROUuKwgGqzTfYApUL)jn6U;fX)_LOVPrnu zFf0>OZ6=`=NogcL3pFb-y$~kah)_$OghMI=en(G4{tZQPJMC`!;Kk5P6qA`nI2v!V zrz|Q_s^}&wdX<68{~ggESu`xcVc*5pDNtX&@*#+!BdS6gVf%=1{2&NrCZa)Zk>%Kn zp)IW~1@Ge%c;OVY=vuwUR4EXS9Y_?*$I&{vk$Y7^4CYqn?g?aPn<)`kMwGVRu zRWY6ST?6Uq=WiQ(`&TOKe4BsYE}cKs*n72GA?5PrI*RPh)pm|Rcta~C5|lc(XYb~j z=5j-_krG7a$GfdxxZk>7($+yJe0>Wp9sFjY)XQQJGUM#sGXlAcmPLOk_c;u zh6_W87Y{XmcF(lF?tVV;_)7r(D$FX)_$qUZXyJP zknpC6gj%D#451m2%$xFli0XGRv%ZJmUO_5L<_-}+^v=>_zjP}+qM7z literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.oidc.OidcUserInfo.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.oidc.OidcUserInfo.serialized new file mode 100644 index 0000000000000000000000000000000000000000..be89d912a67f9ce6228c166d0c1cdfa18daf42b6 GIT binary patch literal 328 zcmYL_ze)o^5XR?D&iJR$H2MPCZa}QGFen5Ar>WA2h2!2_Zq4rPIlD2J#Tgv2o%Ssn>ju_{M7qoJwgv2Y|Oto%$^ocQ8ENXsOw z%A^>CGIYfKGPlq~tYe5$Sd|);aUQnPJAA;-#4Fvo!0NcJ!j}C-Pb_-k%xX>2D_gp~ zVQFlpa)q~=g7@p``PYB9PMd*|xraxH8hm$&r>Y!Nc8;zHz~TJzdRxzb0&oSJC?z>J z;EcFtXfK(6chdTJecQQtUxBy>8`@&lL+LH|Vci!@uCO|*tCMbXRy17;8i$NLBxF!& R5A8;~RHLahg}0a*{s0qwYyAKK literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser.serialized new file mode 100644 index 0000000000000000000000000000000000000000..62cc96f4be13e82d1896b91afb4ec428b2d48c6d GIT binary patch literal 2051 zcmbVNL2MgE6#cQ?I7u5CD6|csr9>bh)a(kVkVq;OCuynGrABqbp#sWecbuJOyt~Z) zB@PFa16)Bu3KuS1kT_IA;sWZ0B95GTYDEi2ZX7ruBuXp(pRK)d8Y&A*{<}MW=FR_a z-v4v?ci5F0n2*hhlO{$*D=j11axFHiPAdJ(C|q}9kzx3Pt((vTJ$yL$G`34Qf8 zEx178`o2ulh6$u0rXDWV+?&0t`tuJz?tkZ68A_*MOvfTPC47uc9Y!%UG7V!LbdnRV zSc>+njnH#~{#Ar>nk^$#$zGz+>04yMU|A7U8;3r<_Q~fPOCu3?ye=$*vPtL=} zPjCNqI48tM*uUkFifK8=JCa2E-_X}DZ2s~bf$;v@Xd(ZV#g8^W`|jIgW%4I$dpPTR z91J0h`dU!|%&4HlX}_N!4Cg}#6+VBO(#o_xlE0T#N>VxyA||u0t7Bj2Fiz2>;Y=Dr zkoG*sEeWXF(rHSw`PPS5rlwDSLt9e-;}j=AI63?5aB}Ah-c<=qSqo40d`AyjUkB{? zgtd?Mg$NIvc&R z>}|)n1KeMqwiY}X?Uv*^4-9+o7z3T%T8I2$oy0UhN30#YU)Jm#XY&x7y=?ZeIml*` z&EssQ*gV1J5Sznnj<9)(&2$Gy0gd|N!o{WL$#Xn3PSn3VFcFseE$#9*l>WJJ>avwQ zNlmD>O`t;b9v>LBww$7&;?5biO}vlUQPQEJlBezk`|_G%HM;dEMG$1xZOT;wN>|n-Dcp&A&yd*|xt5Ymq}O0?ajGgL&|EYwy{fT%D$BCtAL0h+P@%Aa@F?Vq)DT7h$Zq d(0KjiMI%LENqq@ncRsX6btyZ&upEAS@n6F2h>rjO literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority.serialized new file mode 100644 index 0000000000000000000000000000000000000000..a9da1af34f03426915cbf3c7e00402a3009f1928 GIT binary patch literal 1313 zcmbVLzi-n(6n;sXx+yL6XZj-p5`vA|qGF+~1Zhjvh|owa1qp%RVqcSM9oyWU8#BPf z+zp9;0I3q47#LV6;xAx@je(7UfeD0o@6yl~7$PG1vmL+pz5BlVZtn|Zm4wAm)@&6? z5!5y$Yw%_$w`|4TSPK2n4q2@0)3zH*Zim8i?O1VXSMX^aABy-fq`EhSAKrzXkJr+W zE`ue!8{rlYG|ZNf-1&ptX_GrGa)-zrB6o5(PY82qWPGi0`o!^^eCjEnh1VM}s(SsSw4za}rbMeAqs6Sx^>c zB&4!1BQdapn2H#gI)RY*6Brl}0}|rdX}GE_sGM~6-FtqYzxVm`tFKT}66S^MxGIq> z?zAQ8(Y}zIuA*TonI5=;q`EufhC)&|RaCl*v`td3TMZKrFaq{*xC2EGDnx6^Hd0NM zhU4CS@{rV1&3OF=N#;DL8Z?2xY=7RT8uPUn2{gtK*XcmR$q@)Aai{JhcHEiE5~O|6 zV7ms}T@akpP%{y$3qjly5ocg!_NuAJ?|NTtX1D|2|PL|90{5*0VRSXN#Cm`p2-} z+O7gc?6L8GcFTQc3}>t}uwueN!`Z|2U~25KfeD}|PZ@nz_ZrLm*d$uP%q2UP51x}r zR?JY%Ktr|F2yU;;tOx$mEt|o1n*VIaUYkFBT%Yw4POnN5YZ@KW4|;S$3O?qhq2e#J WZZ54$N+L_~43Zdy`ltlWEQ{ZvS~~*( literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.user.OAuth2UserAuthority.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.core.user.OAuth2UserAuthority.serialized new file mode 100644 index 0000000000000000000000000000000000000000..7e4a1ea71126c00e7a2015c60d6e8091f37bc3bf GIT binary patch literal 417 zcmYL_y-ve05XUbm75y0akboC}HI*th1_*_!NTG$c89+$jCT?Qt#=*YOvhX%Y3_L^; zPr%AMFfcG7A?yIECChjA|M$N;KfOayNpOXX9Fpt9nACug`J=oJ!x4UH=9pmUU1m`qBCK9#jCu0$^AtM7$b)}Do z$8Y>x+_%B*rQbX3B9t3l|KPOmo}K&vC&-Cv literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.BadJwtException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.BadJwtException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..5c30d3947e56fd1d61a2813bebccd833cfbb059d GIT binary patch literal 16587 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 DHe}?M literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.Jwt.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.Jwt.serialized new file mode 100644 index 0000000000000000000000000000000000000000..06f8ee1babad6d1d3feecb7b8b1dff1079308921 GIT binary patch literal 831 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.JwtEncodingException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.JwtEncodingException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..f9b8f14f8f7b8d2e286f8a1fa0264dd69865ae20 GIT binary patch literal 16594 zcmeHOX^b4j6|PwuUtloC2iV{Pk8Qxvz5rvq0XFOPf!7DSwz-XZdwO@L@{|+0O*y*F zIXNMnJ11mc-3u!O*f;6<4H<;>`|tLLfB#X>6J0_~M&ibqph{4DSeZC5{KU!w3dM?( z?zrnz-TH_#(5%a#<{MfsJTzzTf*T+EhXTAT1vC(U{ABfQD|TJ^ZVSZG(I}j7hpPUE z@7=U<=fpdIm?dWSiwiur8T(Q!?FX*SAiCK=?&=o{E53At>Ont_-4TLYY{3mmC~VjL zVnHljZ|rE~dIYiR{tQSm&{y?qeq9pEC2_OShzOxyF~*1(2z_pckQ{ZyZe)?cS7=p5aMMr?8;O_Efd`NXP)11^Iyqs^S&(R z42Mz8mHWklYEbtNHETnD)Gsdas%{+jhi=7>4v}Agl{x+5;)+iL5WW}Vx_91iQ1vaS z1vOuehLucDSZQWp7S_1uxF5yjNsMT+Q4*qbQi@q_qanrKDg9}XeRfcLYHt|%8r7g5 z1#UGs4TB%`V@V3$+&QoVwUJ4xZf&UImdU&_@XFh_Hh5{KeOaC?aqw}8Q*PB3*Z z2m5Z}M}u?}gki#q!opO?jT4?b{UZ)D&-PLvhrtSE{5* z=UD7VgXCn@z^@n*N21Uprwbz~)=dqdd00&TZe|boakDCIZf@4xaO^0Me`;>_DPE;m zTgZzZ)||{IPFfjwOf#^djXDhPdt8qkSc)aN9>xwND{Q&$W3*`2pjj)$r6bCA{-Lnq zA9t%w+bUg8GrSb-*w~-AMqhn|mKL#*>(N3kOsM23i02JQ>+!Wgii^C+ccp)L=%nvu zjPgzn6hE)Er8}7ekm7QMYak4z!na?0usyAZr;+Eh(RE)Gg^{u8rS?xP-ZC$&R|4!8 zZgmeu)WdZKvWakbxdfCFITz)5v%$}3o>sNtsizU_uQjTqE&XNez0F4RmeB~W=$oXN zqo`UyU4J$}i|}LyOoS;4)ju}s*pe{C1_tru@yZx#^LNTg7l0~HCUpv*XQA)QI^Le)z za}^jRwIRCnCxV; z9!RHAbtU#5#|vw78?w zQu(kc`zCb_-Xu2Ym{{>+FA5Np9GFd+3}3s=fD~7? z8`3UvMP?bKz~vpbIQum-J*nc&j-$Crwt>-r^7Y6Y4Lr);%^9#atYWK1=vv;|sd<^J zqP*Fyq3I8do+5nVgr`<)%Bs!ilR0qwoU{S@rUUps14yxJhN2>?1y4uac#M)=`==If zrB=0;asbDzI2iYBx?d zh7ue6r-hm0Mzkem_Ewd`cgWUGo2_XjeyT;{#jRSjF;;RD@H3;sbqpZIJu@`(Af4aI zfql2K&vJAaUTunDL}%vl09P1J$FPY`4_ru6fX11=4+5{Whl?Y#Sm@Ordz9N(Xi6$fM25>oYGEEvMot6b^Vu!Y)px0 zd$cNSAFXv}SBkkBZvnc}k`DF1qr8=|p(#{jCmJGaWB$}?>#9^r^eS;Z4%SwsQIiM7#_1RsN@^nPoVi4 zny=dcPosyKOnG?H2AGzIrwTB9!^SXWUQD~<1s_xRQ3XLxlg!Qv#2h8wz?U5Wp#N(P zUFP6vIxDDCT2HlVlmC%{OC|hbY8i^) zQ+=n8(W`?j-83GiYaBYT=`4HA6r;t2Urp>%noVD&3-B|emJ`zf?8*V8#bkZ_PtSM= zL*HxxUp5iQQR^OdkWXEvu7pXq<|g51hD)`%NU^2Bcv=1FgLc1GD%D$ZJ;i!DW;nQ* zzDm3UWH{K&0L9lfc@B~iR?5Wkv~ZPl7W1)DleuYHL5)V~Y?a`Y;);;b2Nqt zuF!-JO-q>Tf(=)W_EdwBI#)(UeQ(TS!|;HU;kFsU8?AVD@Yul2QBzuqW*L4KSO;T^ z(Oi}RAnC+cXit3@qK!l@JuPtDMvxXUCEXXsj-+))La1t8Km(u? zBqbM2&asr5>g-sC4V$Uki5?dMrl&X9q{{-MBBMVku52@sctUA?La~bq(4P{;;tKR3 zss)bgqDfMIR#oGx+6`&t&sBNS7C4QaRpHj1+UAsb>dma7^|a2?A^l(q zI{Ga!z0=as6u|`V4{2+M`@wed)Yh)QW-)zVWXs#YW0IZsoEG^^Ma5X%o$+VwyQfK{fkW zH3y)#el!OYrD8knqaM7eyH%^81E%-#&ZHP)i;1QAu0$&(5{hrZ`TgFVObaX=L35WB zvvKE2Ady&|USsKa+x7*sH}d8^CZXF2@+>9GNF;DoMxKm1wR)v&zQs7yxV6kNY^s^;&A4GEqP3Bcc>M(a{Z-j6C? z1X1>C7hgkYG70c=1pp?9!(qGvfX`bB-aS}~F>o`Ls#AcPy)M|uEHZTP41}V~2SL zW3Bg6?;8wl08||vP^y}ZuZ%6&LzqMg@ZH4R2#0TSg!~>RvfUV?eU5<1i?ICA#_~2} z;Xu&7yxUR9u_H_!0d+#Iqgn|SdZC`5p?L+(8)$4Dks>>)n4P;KPe+)jGz(7OM#J0w z`2t;G#k?agpmH?yc)pN%E*=eZLJ8>fT)LCg0t9;s2QWYpc=TkJFp~KKzX9Mv8=%8d8|JmCgz*WP zrDy}N*+{V%XfDIgj3zf409+AqIj6MTW>OWSFov(v3czR1!{?$P1k9_@tVXjYp-;Y& zDYjtnYJ2bmqFkwLW}5m0W~%#A2w1uXjTuzM3v^wF275~BdTR1n@aJD~04HsV0n=0p zFcYhyv;$4T$`#I`URb>dbOG)G4wx8YBO(FrH^y>=AHDJOK>a_l#3u{rsmt$cq(-PL&8JxupZn?{kVwwg_ zXtGriqH;otIc}>Z#hz*B(-2RzAw9kN!^qd9hWsdS>%k*%_^=;KGVrGEksX+gTvB%% z$7*hs+$#gGx^-Jix#V1uP;7&==SguB1Pq#l)AWLnzN8;D-MTaCH~q+!VdOM|*mL$I zKX>_F$n8?BDmH4)3-oB;=D1&TytM&_u^$c5B&!F0&9FEeg&s}1Fp}c( z=@GOH%W1xw(MSBaU6-~rH)v^gd?so3Dp{piUn-04){-nFNs0nIrUls0K>#EBZr7s; zEX9gk3sVP?)wW*uF@>W1u-oON+$F zwP+<5CtUIz#0y5ED!#6e;#@ECUFjb>cHH+ePI)(nil0~6+8xg!NO7T}btDX>qPI`a zV0%~}52KmW!PWy&6h_9U%j}<8y$igsSql*C-TH2dsE3-2WHaILavnG(dM?e&W}~0d zGF{R^roL9NuhFWLwe*+q_ZFMX+r}bW@wH8gd5Wur)bo)+T7s{o6y|A-afI>v+fhW3 zT?ZbHh<=<57XPC^3S-wEuboI^L*$Rt$#Y3i@M{M+RQ7plg>It(^MdAhc*4vy3hH7b zHE$`Uv&*mhqZFf>qgg^q|Mk;#8>93}v5)tCjj$Oz*Z`d)%0M(* zj?zyG-Yaq~dRCpQtfY7+Hpl+)SIvpjkq(@&4^ud_m?3CVixd~6>~xz^Y>&l8?9gns zrV1Tzw*uM%9G{oF<4@A&glX?6A@`A6J#tmpq@*L?q_vvgLag>{Hm4-o9qEEo%7fQ3 zlB_W5A9I6dm4Zu97t=)7hK>AIjcg`@S@?o3Oe%(MTAm5~|x-dwbVO#Qf z<^}LD0A*;a3?AkS{92qVUTzeA9<|A;$%N*+0qy~~*G5ntMDzUy;Q<>V-BlU2Xr-MP zN6F$XpUW>GQlzccBKla2JEb_!!&>iavh&hC1X=49EJ%usuU=b~2Mt21YX*UB#Jh9{ zgg-F_DL64yp*M*QIwscq*oy)LB?oR(F2mO@(;>y$PF;G6tj;Wh6uEk%tf5xQ0t8?`L+Rg^coH7xysG2}^n(fFlm#ipX# zfJVfVrn!vtW+iOj9I9_dSVMJ%<@c>sC zPS?1JP2={lMvys)e5}R1YnEc#)33VAA93l}xofKFHUiI<71SRs*yep^3cxe1i0~pK zNWn+wZemlCf`GRjo!0S+(fLWXlWkHEjgw#wo6n-}xx&=n9Y%CS9}noNCEe4J_P<4m z_i|3WIW<1SH)by7CySF$?S+NG$YHuyqNEEPa=Evdl>p|11OeV50XC)r#F8da!y$PG zTyN}9E3TT)>Y)+Jrzk}+EpakKTsqsd3W9O zv+c&*-^**QWJl}E0Gz3OcBE6BCVU^1UIQqjH1A&0<*HPdz9@31aG6&%Uqf^&LwP1H zhG?TU&Du;$N0e2M_zl{@DeVL&+mobI&wq)@l_?YLkD|f$(pqP6rI@ejmXNEg=+gc- zR<|%UfHLLDk?5M0cRf7bKKRPPYqGuD=^h7oYXHV)(!Gs2iUkCf z_M=(l)cE?E&0@wJHJ_D(!q;sID)~n9;{Z-G zWH}bbP_!$a^D%`VRS@Jf$?UA4#8ILUZ{V=2jpR?^>uEYGs8d>By>7GTFqvcj#k4Za z^pDvY71;kgYt7izHyuJRi=I+Qi^=-8baReJK}xl`NU^!Zd0G4EgZ7{{D%D$ZE#-DPY9v?;RiQxEPMa8^{MsfjK~lm> zg;_}Q?q=c&Gnbo=fDAo23>(U;sM_3*61fi;LRcVtooqn9$LrGDc zVZ3(DO0JW|ah6PC0w~}im#(Y@xI~IYxP*w~>gLU+Km(+QNJ}o5oMtUG*SWC_TeeWQ z5kD>j%*=17O_v46M822nF*C@!m)=6(4P{=@(T1pss)bgqDfMIR#oG*ow~H~ z=c_zvGbT;$SS1dy*^ygSu0cOX!#vVPAcgnwEjN?tT9$4dlSfRj_#IaMBd16!y{>n!ck52j$i-;>bWEgMY}Oz>!swsuVX zyZN)amF`N?pW07zqIT2v!s9tF%!UJ8dK;}Py*<;N%mKQAqql!eb!m=gY&Ne^Ru&Vp z!V}vBR;YU0be(ue!S~PFd1OOGr8KZ=?Wqz~uQ`1@K}}|(%pt`^rR~OB8EGCNhId$o zpPu2+R4n~QA6}2CcM2T3)s#)RUpX_9cF|larkR5jOtY6wvmbW517I*QDz?)BYC*bt zvo=8&Lhs?7NeRXt6D#vgiB&2)ZC((8&#aY0kmIlvb8+WND3Mr~USsK;BH<&J;A<(sGFy;s2H|{s zVXqb|Z3H8!ScSd|0Wwe4RFaP7H2@a_thFgTh8E7w*5T_?fDDEFqcL_u+|XPF zxB_5G)AOh2knPfx)4VE`8(lK?-D7M^k`LZJ-6*R76c)jI3sgka@E4pkZ-8zU~3YAawbr1LIBt@HJ=VEXos& zW#AZCEd76gag2~sn6ek|lO+gd8%sBbtPB~1vp1oFU#4GfEcq0^o(9O=Dt&Aa`0BTK zFZKRR2rn9hXYutMK*krm|FOGMgy$1?BOJcP5t5%ae+Q%4dG-+jE-$0hQ;msiZ1ULeftFJ_X1mg63|bBB-xY zDImOu7O2^&*7o{L2oKGKz}L@N7L^5dFng*)5CY}30G9x)OZby~nPLk%*V~;BA&aSC zKBE>ps(#t5gLaXe^T&|<#RsfhLz3PN{ALlXVuDa51JLdCTWP{TlfV~9L lbq}78Fwgzy%--{&vx`e$3_JvI7+~6)TOb^1F~8x-{{xpI#BBfo literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.JwtValidationException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.jwt.JwtValidationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..212b70426110328919365ef0b88bb90eab8f122b GIT binary patch literal 11427 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.BearerTokenError.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.BearerTokenError.serialized new file mode 100644 index 0000000000000000000000000000000000000000..3ce7ca5624e7afec027fe87fe783555adf58e831 GIT binary patch literal 473 zcma)&OHKko5QZy5jPbeP1SZ6#-6}p3i6$T<7)MA1f^lOyLjfbh^jKYkxMArLJcVcQ zDxSbQ*gX<4#)ZA=>aJ9M-~ajnONOATX&_8OVLa%Q8snp;kuVsf6!MeMD&^t6t!aWJ z2#roDKv6+ONNqjBxJIPu?CrGp{!)1M!1G~s$egrTacUU0eLG+NnlD|4tbezae2Wt3 z1R3)Fy_zT)sdyk;?5Ijpf`k7+2AWVb%MLh$g6w#a2RR?sdT0U)6ApF!05iwAf)4#$ z_i+A@v(DLdnlNmH@kB*oZ;PGfqQ0BgnS(izlC_SE2$B23sSbG@N$Lkj$A`NGo10t{`qMU*E@}Jr(JJeU)c>Q6{NlF?*%RklVJYO<|@uyy5c zr+zm3jW_?ZPl$y`oaqI7EsUf$HV8^+WJ?7#AGj>i-?;Dx(SMIz`D~x4jEXffio!^V zW21z8AjkSZ!uo(_eE`S@H1ZLAcapd|u_RZ_P8UXYum1R!_eHFdD?_H-2b7DgyXvqtm=9G*^#RqB)KaNL3zb>N-v?ahw|ESnh zmo$LM4`JN&uAdGX(t+C0lxjAtXL`bVCj+yw$vu~46qA-SqJ?%vh{{V!tn%A!C7xVT zLnnBg{TO~@#qjrjr=e3EpL1U%3?um`cxk~IA0RNb%A^3q1E9zD|5OR||b z6i31B79}15gF%a6TD2ggCnckn-|%K+OGds5Bd;06HSfvfby$+xqhE>b#ZE(cz4=>$mC+lB9QutkeW4StF2jOX6Y_)=23r{- z=G4!{%~{Q>5_?N|F~f$Fe&VFaz~>AD2f8p|c|YUVNP(5un(JZgKytS`uV)x7ntf=h zN_=oeubsRQ*5zft(Q#L$@5>CYL^n3hC8{yfnxU;lY~*^(BsV5h@)X4PEl2m^>BCCg zp1@uV=R%kH?-W66k@S@uo0^lg%=*2R1I4T7;+o3=D6vE18V^II@trfu?tV7NpOKPv zSr+y^cKJW(z8Xo~SqoeB0B+B3oFU^o)?y%+2wS2ppp=MYbDlRB{JP=ko-RBMwu5uc zc7p_Ko~&vdchS6IC2|5!k1DZNQ+0s){=ov>fv2tH5okNIF_J@_C?Y3dY!n+$87Ci$ z|D}Iq@gAJ3Uro(OB&Qp+a*6$r_49012WxsWAEgi02CaFDJQ6%)|HT8;eYh0Qux!W~ zGH0!sEFwMsy-N*Sp$sa~&yHovhwof___%k-JHmcoGg0q*gsAUGJx?+PO;Cy3^h1gc zavDK%M&ls!x@sMZk*qeBY_8J^l-F+f3U11)h0SKziaq#BZ(IwAwB-`LlAYh0>oHn& zyOxrsoyZEV<|l^I(IqIsWs~J}h{3*6j}mvLHP*m;MHIAzEjNy*H0+5veBPqJn3 z`&U@13=E34ZCi8Cl6lcfYU^LS~Y%~;7v}dToP3wpnQ;&foS@{jJ8~55C+E|4z4aeI| zI1x@?4g<4~tA|`!J!g1*{6={Np68VKfT0-h46?Mec~@ehCt8Un+Ic2gVmo9#)eMk- z8cjwMzHMf=)DBRxg-Fnwq^LjasF4NJK|3JdWu)34B#Yrn@;8|lnrSpM6l_^I=nK}h zs4KPHS9pEaMXQww-M@&2*XxQ4pjQxMz6F?a0aCA82S8CukE5I7%8%aAx|HMAdl6$i zk*Af|RKs2$Z1LiyJ_G^oer6;E#?zoX6&3+I;dHJU0G`}Z0DsRUl;9Xr8~MaG7@Js^ zaV-j9lsqh(UNSrl+W{r+?lxppWM^g@q`=jTcRy#C8B7IlJ&xffaRajfh4xWxmQLpl z&mI?t!UkM5OxN~er{QItw({0nL()HH%4n{kS)h}L-mxjGuH3(n1IOzxxd7}-S?(HN z0pMGVL5Y(qoGJof@HFYiS1B%bU(NJ(8Bv>Aj^kt&2lKMASUu*lsD%wSK85Df1s1=` zETUm|_9D51?x-JeQETxi;h348))BLzzy|-B$*lDwa!DEAs-yiL**f8}wd{<)RG9IW zq7+?>Y(RLKwTSS2Mi2HeMkPM6!eySIqdqCH^y_CGMaRNx*A_-}yq*t`?eKbrO=KE( zrkX+KB=U}7^YRMWbk(mqET?@sp$;#${AN&dc?I?t3$pnU6Gg+Wq6qL?44?#`wtKNn zNeBYku65eOJI2H(*+G^`fi(_-d0oGWv2PVr|4bOsX?{MStW4_D5%#}Mf%g^;yg4); z<2itULL4e{x0(=IRu^w$affdYEMBk5RGlNW=wMEa_Fq6tA`mIno=?L zA|YCBe8039djeYZ)2tXA!NywLP7nDt=S?dm_;?*nPQI0Q3Gv>7dSi& z(*l(#8tpQS{~?+`LepgxF{A~Jr@ugxUut(XF#eY%K(qkpy^e-$NU5M!EKQ($yM!Fc z5xqY|b7OMqX&c=etZxI+`!K0ADV_@RAF#ED!lO83@1PVpuXRCa<{W z9Wp<w_$18pkM&LkBh!Wq)Xi(Q?8Zp28|u z`q69v9~rUOj9@o_lgxUGA+0-`<9~X_gBg0M0DkASK+QUN*a<#$*}4)wdNlVDUNc;3 z=c2@+lEur)&m6QzjZmrHlItm#(97iCT%$*($-Q#LlvACRJQFdo;!p>@T4mN_B7FR}QW$miNhUEdd z#)nn}Z>2Kp8jl0a95t1@(Cor%MnClGU~D^@9T@!O7leL2~PV!JnRG0bQEX_8qlf`is zO(FtJpoUnw0yA5on*RnQA>z1t=#VYY0O&cIB^OL?@+=M2)v=;-zPV%qdXEu3E(9!3 zZ=yrV0xKdbKP7f|8A&vua(_axj|$NLLlny^&=;r{IB$w3N%>hD68dS8 zD7n$F1ve3B11>B+38V=STjX`iJ-j@thKIZqilx+d7tNpswIb7{@+WTBW9)_!YPp$?BN zHHeE8oPRcQAQ{3crH0k;MwO^W-5X>DwULb$hZ1*{_S@CUaPtBY{FtMhOB0+}jFoIs z$**Cqsj}(TDw|M0xIB4%PqJsy zjPZ(zZTYT5DwQ)Xt*3}OAJr>}Vk(!FSdE-3fka|Yn#MAoii|{D9ci`|r*mI;Yx^6g zUVG)A=-j!`UTAlKY7_DQs=ew`u@Rj>Xe2l^+UC+fn$Q<;zJi_aP8Yy3DA;rF2Grdy zfR4@4y$8*Q(Cl>q_^8Us1wLq03mEd(;s9{K!C>yeq5D2Gd|I}i1uOP_bTf$*;3EzO zoqEp!crL(OB^WXQb0q+oAdGzilQ;rU7VN_=30Z<6QzgD`0rVX(yk-F2C;>R-kSJUb z1N`X(K$}c-Jr3wZgBYYBLtYGi;Ws7fvGV8-Mp7<1$35>Zom;;zg77JQkE5Yzy&q7}r z0VAw4ONlxKh}>E1x3fqAGFLfn)?E+KxrSx|&7#W#2W>3sPhqThCE&;CW`GpKFS{7N zPy%4xTk13bm14*M+{%y&zTD1EfJOZ^NbBopGTEkgtf+a16!TR-S`I+h*8%lSG%Wed zTCjs;9#Vi;lI4aue4TZb)2+OZe+PItE=cc@7HZjmdDX@8CS&;y`d>ry-8>eM>Zugj z6Q-VkdLh?S4TTE8m=61-|5p_vD%pZ@jv4+0Pp_9O3zc8d`+hFm^`r&2Z=2l7s53vnBUL1x-NQDz!nYzEmQt zm6nHZNt4ux1Hl`UCe~xwP*N8zN#vDPXjVh`88cv$7nQXDSXTge8$B>2-AZiVTMl5w zMLH1M-9n@qcc@RA;C|l;K>~@*dMJjxJ z4;oGuWzNGlqfi9Q(27bG&HjWw=}M*ugTaID;B&BY6(pAQmo~eEFLfUEBN#+tAz#!$ zmn||>4%2Jqq<|RzJ+DN7r!GZ;Whw~x@+6v5Xma^}be%@?7&U1E$!3-+gDl7U0rEs9 z>5iUZG&n@1DNu;_MgGR-uq)@>g}IRgHz=J)bAg&vTS4;$rg;&A+59gCdq@uBL!Ge2 z(~=oo0DPj&^q!%=yN|>$^2zZEe+=n z=x-GFm;NvUAO_A_F#4^4H8EWfmBgLsryoY(zffF`DZb?3{LBM{MK^&PePY$9xGnj6 z8cI?#)=|+f!RiqG)r09+ML&3`e(nH5N`W(+|CRqla%InAUYnJ)rq zF#}hbtGGoK7p^hDsATepm||{r-V>sK4Ne0|4@Pl0)SWO3#YtwV{yb?#cr5Nw z=LK?xz@8?Bxhk?F4>6ky&@=1knGt%XB_uNw5n-~Uizd4O-+ET_!PTjJ`@zOD?x?+@ zfuxU<_QXDBLuz<0ppYCPU!8z_P&f|c2E~K)nr)^icYK{(=%s)a8?ZVCR>z26_1BHN z6=Cxdr$vY5xvy?@tl#?SFRyp%`ZhYSqDW`r7<>n_-E?nv@iA)#$n2%(et%RuO<5RK zCiEbvm9dwslynBYhp$}ip8J}fOOsTNH2VBS7J5+LRJ9Q;Y#LY;Xe^CpGomzOjPZ)+ zuu(}}#cV`dFUNEN7B@=au=zY016sQi{h_Bw>)}Fv`1JVL@Zk{SZifK&EgC?T6&I@M z8CINM=%J`QaTpDG(k@v=D3-Nt5T&kM2k_K~Yp7|?#=|=>piJ~0M0Wcb#I$?Sm};We z*hI9=S{8WLguC|53ggBTC6F{kk?}3zEtK;Z)0)u|IWpq`SR?ljdYVZP* z?V6Lg(->p&%~P*_zV`8bA1@~)^s4O*cw)KhkYbTKmFmt zs?Sd8=Fws^hfPog6db*83dVyh;T}W|38Alh&P*+zAy%S+QyWKjk9hCMALrgS=B`5r zpEo0sV}NS1hvQNPT96BKI;P+fC<7Dr#{1|0Q8$|hqOMxe-gPn$#Jt%@{W84T)cpOI zU#))rN}4o`kTyq9djv+RkU7$-${?AtaO}`~a<8ub@@fnG?}WcrMbb2|0iUkerEb*z zy4(HwyZ82darw2(RS4q*Nm0<=iAa3Oi5q$Wbo=Omfe(yYi~aEG(Vwq(zci{z3`eFE z*-%!>^LLx3C!rr&(67QukVn|PKH~y*f2#% z+AWrn`pjHA^f%^2K%Y&KI1#a}0orY_o>njk7`5SX6-Eaztl1WhR|eXWG&BxCVYx;D zf-uGp=igetb4S;$pJbXe=SWK#fF2Y!gOTM4sh}It`moV;g&LQ*6V({5%fi=92wHp# zrYS6;zeM0ZEm=u#3B7-7KNwCmG1Y;vL`9L5iSG?Tz#1_RGRr_&`80!ABNAw+2Luc_ z>Rx_K&R%=>&)=X>$7|AtPXkB}`eqU-a%ArOff`b+mc!w*=rjjdI-Q>0zRBL6UfbW? z)5|BfDbf-^T*zI(ZqgPk>WFRH+g7Of_7jT>ODk&OaPBYdx<$^Osyp-9`< zVBz5KX{=IxX<4+SD^_1?kJf6{LyY>+tkusq0X7fEY|e9cirF=pY`b{iWT$iSFOASL zsz+o&+Dz(}Y}~02bh`yjFPfcb^ijL;YB!pGG<(ow(F~#)MUzAG6q={ew?*PWMFs literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..1e0c8c6a082eb5e8415cbe249a7832adc63382a7 GIT binary patch literal 684 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.provisioning.MutableUser.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.provisioning.MutableUser.serialized new file mode 100644 index 0000000000000000000000000000000000000000..d93e5f83da7494890d3d994663f715c077a99904 GIT binary patch literal 190 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal.serialized new file mode 100644 index 0000000000000000000000000000000000000000..48f76f0f6bed0cba42222c21c6491332840725ba GIT binary patch literal 357 zcmaKoFHgik5XC1=F(mv6AweA&^fp0Y4nqKONl8df9d2podJEg_GCNy(2m}clhr}X4 zBKR150D?vF0dVXd0u=VWm*0CcPoFU72(Y0_I7`Y@W0EyqG;}5$7M>JOg_E`35|)}) zl}JISRuw3u=Ls5Cg=Ezjv5#ZvH4kHW_t(PmC}0a^rM|m<{3?6B+XT`a<|Q+!6VK=v zmhw~C%FMIU*`c)k98wczFs%F-TAC^whP~-Nl+a+7lsT>iCu&) z|AFQq-|uWVbv-tT*d+EEt+9yX-R41Uc^axm+p9OnZy&D-KxY|JAFgFsaH_T%<0I@O UQa1jf#IDPiwdb9O`}Y~<8#~8?00000 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2Authentication.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2Authentication.serialized new file mode 100644 index 0000000000000000000000000000000000000000..929b7d477bdfc7f67662f3f3326f5b6dd0b920cc GIT binary patch literal 1187 zcma)6F>ljQ5WcuBG(g%45QrgVDTo2tf`Ngsq_hf^TopAGL$@%WP@4NdhuYbX)l5jne4M)Y2 zhmE>qP3%N+-BIYbB-dR>v8Fih#4>90042p`oBQanmR`kBbDwD*g-+FEm3PHPpXWa= zgx)s`ED!Pq-sdqB8ZLU&&>d*#ZZ~xONTQp8!rll7{d1f=L1mhNrtUw`Chk*wN&F_eY#8GKD z)+5r=T)3AbA<#F2A;$Xy)Qk&C$#Pw+tx-lPiF34p(?)}1N}YbGnqCf@QNZh*)dYH6 z>Aw@F&b|HlaMFUD2ezPyhRIA@lVjAM-5ajo=EMx#{c!8|R0^8&@4zP5Nlc`U^ij%E zvaUf-?tLykdC#7XKyDsv#WygC0U+10&6A2|h7R7WHg%4fTGy%aW-PEGS*S5E(@(>M zL?27^RLV%8;bbMThWyf^H+OrvI=7UV*dc0oWoW|=)&`w=18ch%J=+IN?xE|94QcSZ6Jv2k$C#43F=>`V$n3k1Vh~ zIKVWSzt+;miW3=6EU=h7XoqyyTQbHaHfWVHJ=`rV9*l1b2m?$ol{}W(V5)C}zG>9- zTr<{P0>63T=!2E7KR#IiWY8rY@RV-ao1m99Gj43y*75&AMCD861Skc_s#BJ?|bh) z?wx0^32hURiXc@1MM@M`Rw|^G@qXBikwrd=*A*ZyZoVxxSIPKz1F~RG>#?4C~ z+4*l*Pfsu#>+}4N$tSupQA+HdpDku6j~`6kbk0BTdriMR?TvLwC@pCh|K-P*9a**a z>0b;8L4en1fEMDX?`;0iO$Tm%qXaQA6Gs>P$)^0fAKkls|Ap88dWl#{0u20aB9&M_ z3S5IAI%pvejEdC_sr;~cTqcP>O>k=+xN!v~A2=&kCCU$GCt^P!h+EI*K+=JcrfiXh z3FW$^+v&uFP&O>ri4xc9Xw19hmEoHR&aWk$3c@1#W&KX~=DFzk($`A%0I> zg=V!9OQ(p_Zk%$UB{*(bOF0-geD}eF?`++V=^Y`&%S2{VCdtwSf90hY@3`-u{$N0? zct|XpisF{9j*3;yuq{t?Taz*#6>EZ~pCqG^Pd0smY#t1)7!}tvBn?1F!x%TcE2hGx zbf8wXq?(Buxt^%e&B3g0anBhUC$t3^(Ojn{#K0OQmiV2H5=Z8>Y9FQvfvc~WQ@&FHY|x#aTJhog6{3}0~j9G(!Q{>$7IrNDwmu43^xaVEp8sxyehG! zk{2^8RYc$dI1za2VGp}3IIynsBw)&k<$2%8e?~-4e>NGvL0&TGs>#}I`^lM z#0^2zZiF}!{pMkEeJ9%t|bUi4gBDuE6n+tx)@N{bro`yQ%(N?EPf;C^(H12oN zylN%#L3|xhVwtAu01f=31zLx%^>lL3c4TKHhr4l1hlTM`>^xQUl`jGXDS3wy*NBMuredonWT zx}A`YA5XHY@#GvGj2w7sCXbRK=tz_Tqhgm?)pCkq11TFWrPR}o^+744!Oj7}Z4?jB z_+h(FjxB7kd1OFJY|efZXV`Ur#O3CmvLe%UcYKP>6K^P}Z6F}Qif3h9PZfQhdJG)N%5Rb* z+G=}fHPVeWxp{$Be8#{9btlo=GLv7>XgUAWuu57fOuxMJu&L zyUx@~Y=>-QmI3l-(c~=Qt7dic?Eod)2raz{x^6^WH8N*9Xa^L#j8s>KsWMziet~JB z;dRbZ^k(5;FVJh*R)WGT{CwO+tBncWKAK51feWBDh;Baf7+}f;$d2h5K$WHRVRVzd zKJcFOQjJ^hMT{v1P~w^ZdwrajCP5s+DS1$vRvEqy*#RYP={00bWMgg{WWe=(?sASAW`;7s+Y`rdlO6*z zA>9??V1^O}P0!v_hodIWez>j;M)5r1k6kTQXj?Gwg8GW_@j-Q`& z0Y(-A_#y)+abS^4MMMprCj5kU)OGGpv%F14)Mk}qxauVo2{kX)?{ity!Uh|^kLD9- za%=c3vxw#yG@qv9pf~DV*r&y#gyUxV(j!(wK^Oipi&^H!bR^}@R(*Zwldb)WTJdwG z6|XHz(Z$#(Ou)~a3~y%uB|fr9HB)j*k^xJ<@vvj)P`p0cqL}XMNSG;s99e&`sZ5jZ zWGl>FZQe3$o>?TDuKCpu$tj<%yoWBf{Z<&byaM~DOS1V}CW?kPLmA*p44?#G-}~`R zX$S(^u6NqZJI2H(`9YRRi8YSh`L^^GjJ;Gc{ligA=>bk%W@kDs1>dE>do2gv92%eG zOr=lp$?EjeXkleIb&7Id6m)@tdu2ebWKj(4N=*_Be4iNDkui`cGNP78{Eqm+%*jqt zx1aTsQxs28h+=z^)Ixl^FLg?c0sSRHe@6*D;gNJt3*@P=BasiQ@2T(7fqA~)iu>Ee zRcqeS`zY z*G_4vj>#6C;1qU(lB-Et)4+cbk?k1~qmQz{9;Us{>?*O`;H^McTi2)j_tkeXHZ+xD z>_ZT%-^>szUysy#60z=ndK^7h=Cdf!c8I8RGNA&bAUe*$&ILACSI6&O)B2 z1R33%#!)MvB){LR;6(f}bCPY1zp4WI_Z+(`Qp^>g0~5sO))Jp3v&2;1KtHHJLdJ;R zKea$rhDN&# zgN9?CXI&DDj>>`N4_yp8_(u0<&^(9cc^BY$^zbS&9zN>=EXV`nEMWMYi-AwF0)Q11 zor+hzMeave^f*kiCo5QEFH!P0kpEqlb51V2Kz9XwOB-r7UGgtkxNL>*PbotXZ!b=$ zfd17LX_l`e`Ys3YXzRn*i>{#dqlfZFT!Ael%;!sA^t$3vTBX;Jjic?Z!v07rj3bM@ z#+9g;!rP|mfokG!n$hBC+M`CORBtKtRLkkKUF8NW^~MqhgA7oewkdLu)~zxoHe}VR zw6mIz?S{;~3og`3lb;HQ=h=nremL;HF$m} z%&MY_@`Z_~XrIv{bT!ZJ&SR&kws*>u_K+Uo<=E#Bb(2-4L)H$;B+Wx9QD0!bQO$a; zlO;(WP3k|ffB>;{4dzk1Q;AhbLL^Cj=T2Lo0nlT#N-mgO=2aT1OA|%!1I()x(0h>R zaUoz~dgEP67FZEk`6+R8kCD_8YFn509eg?AaPC+y zASxAOXcW!yN+3v~PEz1Hu#rM&-Z6NMFrj@C%s6$~L#B&z@N}O+G&hoMLI8C zIEdWH`rk)P)b2-PleZY$LLBUKth>y?a70YaY2(!|uu`H_GwnTSm5?L%+eD82M9&42 ze&;Tx6owjphZ05g^fzs?tQKeAZfvZYlhV&BJA&Rdyi4@g;>`fM z=E#N|Tm4I##19!4wpBdFr1Kco`Y3Bj}^Z?(y$)VD7TWK^`2 zDq!{`0W%M?XdZ#w#?XwXLd6-V3sXvb(1@T9rjPJ(q8Ve2iS@;<$8$Vnvikyb}>4))2{H@tfA8(;osx-8Ch<|rrHA_daJ(=IGw z4K3v51YdYqIIsp^pd;3yS&!xhchP$-00%uB2i)WWSWX2WtG56?_r^^}*66+s%@#CU zT?|j7hk2;uYa5yzhTmqlcNy zNaO&{RRHXEN${z}7+9wF0zOw&loc3qwL#A8F$Vo44DdMs&J-2^9&mUly%z(92UCFm zjRz>%sCi5UR!^cig~olHWZpobdmPO~5x~4&LN|7k;L3jPaYtr?Y-?u$l>^A$9`L&X zz83wa-&TATT(Nf9)@Je3dM`MrFH`{7BM1ON1;B5575aq;7*S~E6Lkh~KDAM-f*l0Q zAp^)c&&&0+1N?m)%~NQ8*X4l&L*}8!3xJFWXrvr^HYU3-Xti+PY0RAXdH|n$BMPwnqhUR57eBih@ z6R5lj%Qsyt-)Ah}0)pOE-o7f2ePQYgs2_5Dl}xDKi-qw?`yG7!0F5gn5@cV6v&&cO z=?fE8Xu;{9qxlOouT?Sr4g>uVLvNtr)4aI4|JQZ8zN~<%(vZ&kSx$W!-Q@#%zpDkF zw^+}F3I06wya1R+51;3mgU_7+SL&DgG;UZyJ%g|OVXNzx=zkl{_t2EQJjBhHEEacm zreCS7U6H2Vf<0pH=U`53G@0r3Ut5RSEf`AfPf664(^qW;e&*bO&34pQ0pRKqz?roIpO=EhQJpcwRz$T#KeOy?ncQdrwgLm|E_WZk9CaaJ1}C*U(cG2NCtJzYTrg-eP8fU)QA`bK13lB0DKMxr zusbn`r-uI*Y8YU%M|oj^+5=`DMDqZeLZtv*52E3;fW5`*G(P$tVmW&F9L}Zd(Ss{8 zC%CiZDa89Jo`Wm89z5o1?Y*SNf$;gkmDy~ldPKENwphbUmzf$6V ZcNnI>Cg6@aO42h_81t)aN=FAY_CG|5oqYfR literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..cdc6b6b25920853d28136f0fb16cfe8b61a78e81 GIT binary patch literal 6302 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 KaRJFbGXERwNZkYg literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest.serialized new file mode 100644 index 0000000000000000000000000000000000000000..5c4c093b58a95e651e766439789de49afac55aa6 GIT binary patch literal 417 zcmb_YJ5Iwu5FKrlCWwX#{>=jBub`ydcS$^UoZ{~=(yCuv#HBR#)_UD?{uJ|VI!RmSlK)6*|lav9C2<e`l; literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.serialized new file mode 100644 index 0000000000000000000000000000000000000000..b706114187c4d428a2e32015b0022e4bf436cc91 GIT binary patch literal 473 zcmb`Du};H442DfmkpQtUAs*r?u`*>u%779vFq+g{4VUEVa{^)DiI8{|Hg{2 zl}l7^YE#6ioE9upYP!?QY1LBD+pm}RPyb;ONHRDGY*S|yPO}H>aPF|S^FkxSWd?^s zw?=J+R`3dY&;Ns%-USsbbwvFW5yqFe1ZEkW?K+%X+z5|M86>=zen&zM`$AFIycwJi zm45JYjiP-{mgD7^)77u$IL12dT$}U?`!VsjSYd0Y!b#xDJBkv&9Okygy2)}A+Yhyg Br`7-f literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest.serialized new file mode 100644 index 0000000000000000000000000000000000000000..19cfd3c8f9bd0950aac2b086810fee48bf3422ef GIT binary patch literal 736 zcmbV~zi-qq6vtn4JzcqqUmc3X467{=8v{^8MWr0AE`dkts4!Q-Kx9RFKpaQ##i*u_PqN?f)1LYIOAJ&Z}=cMO&rEr1%Fj&wdTYup=F*XEEqUuTLgz$|Yu&l`V~Au%uoAx! z6Ty|Ja-L8C^E*pQcb7eYBZy)c8WCs-ts}VFDQc36kA(5*H!Z5ES8fSQ)A5|~M%EtC zm?I0@|5LhoimqaRQ^4$50cb2-2`DU}uqRbr&Tkrm#jVX`n>y-qK;P5btt#NK$66^& zLzmpG=T)gQxhKW0LXY|5?YrOfbFdb{LJSLK;OaHT%WquBV_EHE_EfmS3+|mh+qtGr ze|f=6SQcsO%fD&;T=` y%hWqu^4~@OMg+^rqftDYARmQsIFE*7RAQQtAS4+2F3o}l2rfHW8igU>6y^_|8vX77 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration$AssertingPartyDetails.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration$AssertingPartyDetails.serialized new file mode 100644 index 0000000000000000000000000000000000000000..55aff67094f6c4b9a588a6ef4a357f01ebe114d2 GIT binary patch literal 2621 zcmdT`Yj6`)6u!Gj-zk(92!bt@1d&R1Hz}zC&d`z;sf4zZqz_vJ*W@x-Q&gbJ@DLx2fHI0$U{riig%L-vz8!p2LDT^s9g2#8_okGlOr3GYU+#~c-Fv=! z&OP7x&e`308pNpzkR-*+sInsXy|oJOgF#7YU{vT1C;|#ID(@35jI2nFf(I&iU1=2D zkWrvlP?5qTLGm*WD2DOwGG0MpM~uN(pkh4gHx)v}3!)k}cxHc@bIM7B0m2pejp#6`ytx^l}b_&Qky29EdaRm3V^?S2-Ei2o<4L=yQBk8Hg7u zBvV*IdQuLCl!LJg{s!oo!>e^ph&~lRsa|_LCyNFkq<|3vsLS^Kh4-ypx3^=e2HWMJ z4giogGauja6+%GA%cj11ngkDyVi?#zP;}MkjMfTCep0-gpC_l?vTeh98c=qS07dA- z9tMk{^saiWml+`$0dpNf@^qFB27^p6mmzhvaGaUtrm`WQ=)qz!`6LAz6qPrrb-cxz zw?qN1|J*M_l)o=&0MesO4U1+51?=p4R>D|6t1w02MQP-)8AN=7?X^L zT_ca+To}d)Ugfv!iVaHyr(=rNqCYG{!ZNmjkj9v22b3S2RKM}?^S9_|K-U1RhetfZ zPqXYmi|}a(VRT3iC}eGCkN)@gMAz4(Ib;B|dM={P;3D+-EgG7l=_Jr*-C_CZz!@)> z8lRMN|D5~JrBYN%9N=0vn^U-?cul!BK7%fI_P-?RXj`dyB$rMs3Az*;uObm1kN*<>t0rQbn{jxKhYv$-q+)#&u9K=h>I4RW2#xNCV?RvAHx zBKi-MM2#L%TgUr_hJ0CQln^gs1ec{xHCs80CD&q^YUOgP^{E!}im&MF{~&`1H6fOE zDh_BO)NsH>sAM`qQDCpKkJc=9Oc`o+sXNKij(-^IpP)_kAy>iP1T8Y!*0$2k9mu(jjviCIZ0S>nFe zKYyya>m^U;^y$_Md%NMf!s(ex>%qIN>5{(7yH(qnW0-Sa-fa2L>f5WTDs3O~g7%ST z^2gsuz0Vn!-f?YZ*~XTPmlu5F-F`aq#F*T?QI_sTef`?m480qV&zu7u0PYRMd?9jzVNb?9P5U2{UaY019M zS+9GiZKYR^w`RjsP;Wah!&tF2!<=rH(nL0BdHn1>as;wl=iln|+U- zIk0ExhKmyxP+zRtIL34RJdJO0Z*SSVwIjROzgph9RJ(WL{8#q(q;Fb%(}{|fVTIq7 zKj%4axwoP8?D$=^O&|T)mUraNi%;VPD|XqMST!`$*|z=S^Vf`L_oO1Te?#iP_6HL1s-(UFQ${*iO&04tCJ9ba^h4x7455=vO9iH;;rkj`36I#2L z>uZ|tI=ZK0eP+;IG10dA^t$$p*58s2KHaq0(9J|%+B{{P`Kw(|CGHq@e3g0%|H{Nw vG58N?sBw!wN6!B+ZjD2XU@|phKR9G&4aXnjiXunx>|$GitGKizB!~V6OyQY& literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.serialized new file mode 100644 index 0000000000000000000000000000000000000000..975206a90cb913f50fc09646fe3e96077c778d3a GIT binary patch literal 5860 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.UnreachableFilterChainException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.UnreachableFilterChainException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..418c3b8ece1a2ae8506494fb0533fd7574991846 GIT binary patch literal 759 zcmZvaziSjh6vtmy^TU%E(P$AxA%%j)Zli&4A*dmdz?#B~HYsFo_ubwkv%8ZwGu~M! zf>>DDTlg2)3Ko`$jg26dmX`hr0pHx^_U?=W)4cb2-}m$5ADA=*cT37bqe&K7i$so{ zlJ-J_i6d#BhaGH&j|)PPv_(_lhEx`5tu17+-c3+jSr*WHfeIjq;cRP2SXhc#Jo-mG z8i+?M9yN|LjVRY+xa|I$b01#9M3-Q3S3DD;5=9nnDutO)_b78Y7PhMD(_8c7MeE&{ z0DuD)h5?UUVP%dL_~Gf)gWB!azx&|DAy^ULkFKvS)UO_#Ahz37cKld=e!X*Zx%2tk zDL5U&WFnkF3zuTA75OToisasAQ?x>=hiHt*n7c5-HLTx5eFtjBiezt_M8d?ioiSwK zSXby?YKdEO5)^O{5s+5+#g@OOYaN@sasOPRDRA zMRxd>*S!DOI>R@FBUCL%+b+{FOPRVcW;X9xo*)@M$(|6`{)(&DmcKX5aq~QYzea`w AHUIzs literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.WebAuthenticationDetails.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.WebAuthenticationDetails.serialized new file mode 100644 index 0000000000000000000000000000000000000000..707931001992083b35f84271930084b00e043bd2 GIT binary patch literal 162 zcmY+7F%E)25Jg8Kg@ug=Sq@-Dq6Q*`i6+_(Fybog;>;l0cp|Un9Yi~S`Cjt=>kXkW zB%UiHEn8O|85`<7UyM-EvDJs^DL2^ZiJaO3)p}?4l*fN$L5j8(_${ad2r`&5HC`z$ sOC}M8S$4)7Mm4%B+Uoagp20*0Hzd>Y3Qri_TZJ%PC##*pczK97AAxZ;O8@`> literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..cf2c149e825fef4417e62ed8e77be5c567df786d GIT binary patch literal 1231 zcmb7@zi!k(5XQ%M7mh&wfKx!HaX6%3+hz{Yx@{VU`f8Wfv@AhH98d%e2!?%f%YGd7q zE^ceH>~Qt;(dUU*AH>M8S z)qFe!2AOWyU{^bAMv+0AO>T&6X6c(|x)AZC2Wir)SX| z_``40cR^8Kg>eyvx~JR?t>)51Qq@%$D`J5Tw>WgBbVJIibyV!6znnT+vQP{80v%6; zwRD4XVYFr}MYsAus7DX!nJOGo`0221sovhivrkW7@3kuc*_7(EbG+EOVh}0o>&vC* zFZLg=RVj+HQvaxI?o!q;oH`hJptMdN{*vmT#wvdTeki7+ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..f8e27087821439f2507f952fd0ca5c8f1faf0c0d GIT binary patch literal 16834 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.serialized new file mode 100644 index 0000000000000000000000000000000000000000..76b18216fdfa72d33328bb729ea933ab50e1c42d GIT binary patch literal 385 zcmb7=K~4iP3`N~(5vmXpf;Csj9H6T~g|=#tkPuUKnKot!Of!SyVNw=I9D@Zn=snnT z5AHxhkzj}L!VBBq|Jj=l*l`3@Q6XGS#unG4R@hc_Cmb#tGQ1WoE`)0M1}&SVVpCaB z6UK_TAk2O`s7ItMqmH#IXbrjP(s_v4p0_1cBb1bWUqqOarWZ7*j32`GSr>;M1& literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.rememberme.CookieTheftException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.rememberme.CookieTheftException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..bcb7d655522cfd5b8bdd4b34e41faea36d859b83 GIT binary patch literal 11333 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 S<`<4W|LmXO+<0hW`WdQyE$S literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..cb6234b9d27a9463e637ae5a999c7081de129a14 GIT binary patch literal 16826 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.session.SessionAuthenticationException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.session.SessionAuthenticationException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..67ca836889b6f6cd5df0d36f5fd4da2370d64465 GIT binary patch literal 11233 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 j$uX8(sUS(j<MHMz7Xv!qh5JT*x#v9u&3HLoNy zIk6-&KMyEZTnwUvA+%d&1z0?wD8D2%8KlCs45<1}kH%Ghwku9d3`{-@Jb9_*Q01N} zB@9A7S&3zd`ZH5JXAe*g$YVvbZ;A#pP>KGU+iWq$HIu6ZYsUUmx9FvMmiV~Ac z98p{icGF3P<$o_V*#2Z7=~2Xk;G1pZrlqtvr)A!o@B7~Oz5TESN1cUR+P0W8RwbN`Ok{wBvdHwC>kX^O zPzP>Q?AiI|!g!}K@J&UsiJd&vNGF9!TJqv7iVI%F(TQzSBv#dob;cv37dr3qXqXmZ zPSF)m7+7-XNMK{GIH2y(H@cGPVQF9;Lt>lTt;r(#Pegx3v<*ypI{z~%AHBe0f2jKp Dg)>OG literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.www.NonceExpiredException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.authentication.www.NonceExpiredException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..e2e04c184257388a1e75712822dfcbb24fbee93d GIT binary patch literal 16807 zcmeHNeQaFC5#Mt{zJL%yLcR%sO9BZz$0QI!(!k+3#Nd2jn}m-x==tv1e$L&!TR4POev{D;U!5>s@)dm$&sYRkvRjDniR3TMuRjL0~qV4=<_wC#F zarT)+NEPuP>vy}eGqW?ZJF_$Uy}yg|0$=oc{)iPce78O_?AtYG-1CoFfm3e!Za8U; zJ42S;3`d=M=$7r!_3GC6__%eztCyXgiH7Stm7a;R)4<5DpL@UYt3$;j9YRd_V#kzt z%bxF8r37M)XZ7qmxBU71k--j8=o3qm)cc(Wnobag;<`S<-G?q9rr&TR{}J@OaH{UtG=hWO3LYj>~Qx%w9wh`~|c8@Gq5 z&Trqje&deux89p2X7`En%62nwLb0?DxH>_!Q$z0T6BkyT&~~f)oglDB2yU?fHxNP} zJMR+8}#imL0ooU0wfyfsX8^%520KVG#d?{5E@zuLa|6rYld#Mv(F8}of8dG zyj-H|S-l7K602VcD$e?UzK$h6AigAKqq!gyvxkY(rXO)2B{-m2i#X`myJ^?1kFVH) z=?x*o%S2{XEXm9Sd;aO?Hr()sM?1v4Tg9AV&#&2GuUJrZ>&}5@ZOHNa#6{(*9Rz)z zU2*&aWS(GXZl73KacBTS>c+V0oj2@O9Rq4X%?U@nN}|WBG!rlv*0|@e;|FBJjA)`! z5Tav8C}!D>MksEZ(vk;w>kOz3(@l1#Pz^Y~YggR|VHksCv!sa5_JJ*kEfg15?b=Yq zE|MA01}t`UZD}-kwTp;Fu?_4l4#kb&(5(|ty-W&q>!IVROiLD{-3 z`nkuU{ek{aEX#H(((}}8$E0|>qPV*S#ZkLnsgfwIy8_1_AZx6;PDPVAK#zw}_2Y zk6Pq~5tTFr@wVpZDtuiXiVGsxL*AHYGXF{zwCp=}=o}mxbIJ+*y_EvRPs_wLmI4UH za*3MvdD9xJ*ep>N#Sqq*@4Y#+}sFGmSmo|;t@U?_o z2W>|_jhybL?~}JshZLWlP7qBN|D``V@~$1L9E*<;-x;ow=A!e%sqE)twN#dyc^gfb zJLRy0%+etgZ5B zBxq^i35Mc)`4OXooO;mg)yM0sxhzIHwxP1IX59^~M%51Cr>wG9t9kXn zf>X8nrGUsK5QSPq{ImNP+)jKe(1WG|^z#(fmXq`TCr<~DGwI~9{sbTIa} zK`Exedl~VV=Xa0VZoNq7kXvC(iGf0~HvW;Gq1QcVa&v7~kWqJM8&(jawkOj;l5VQnCQz>1GKelb$? zaq3ZUBrCg0=f)Mfhc;Gbrs8<1iYL6`!=9`5aWN57996vDa;Cfj&(Tm^q$s+)f+Q_@ zyoX|-En1N!>UBm|qC1>QY#AVb1Wm#cUQ(-@Y6pZ)9igB#NY?|e8TL%54%z|fE+y6Z zZlny;>b}La&+60KKxzi>a4ujhakXR&y2*t_$rx8VG*zsj@Oz1 z;KfY^@H-|E3XUP=As?L$>P)OSLD_e4C|OXOR2jbZ=z&nIX*HxwWJO{d#K6Ubckflq zlw!eakE6JW+`yG(z5vy(3NTxw^sOBbaA|EL>56_TI)BK8ioMD@8sCy>scFirDyaM}) zjBMU#qG;GvWC6a-0HNU9b~|TN6oP=Zc{;7-9i!rtr;_Cm(=_j*3v z=8wCSmWh2j!v0efcrWI_n?vJ+oEo$#KDjXZR9cwt4j-ai6$M?Okcz#l5KZ^&4DW0MbMfU`eh1hhrX=E4!`qPAdLk2zJap+oA zb`H4>2l=bw)?$|o%#;0E+^?orEqO;T-=I(8>yrbWX7BnMki6as)0}%~Emy^BDfzxV zi3DL$e+^IqhvH0`D_W?#m6#R{OR4rdHS*vTc7l?rNm^6Kzlq4kn26FxR$%*RuQR)$ zn5*#SpernCQ~ukFU5pJ)t{B^q5NR9prxs&dK(jtfiop{7%VA`X6uhRploIEay~ZSk&|V($fARW%f}x+%Mj7|*1_f%}t!Id5 ziJkQscH$itZ$BVCNr#1Vobp+8ZyG>upOW%+HHQ=NedZ+I8t>!*{WZbvya;m+XvZM& zc}0fLj9FqTZ=fILAR%K!@0%JZ&(LU>Vf+VZeu$=p7BM6Rjjx}fNvqn_2F8D$14Ify z?`brAhU5xr#?l13cXP;*9MSt8nlqDATifXD0e#lhf}r1r))HPm3ioKgM(#cGt(d?E zyfFdu6XD+G6vhmI4Exa?2aJ!JB&Ll~pVA~^3|}=d$lx1LUqkbCG~X}*p3K4UxCt;V z59v4=2)}7!m@+OVuXx@^bo|H)9EVBTvjP*J5}ES`^1oVh{xrUxp}T^-rIo5xll*ry zTr8u{rj#LwzoBVgsDS>*Ica9EBUfsxlGcZx&zgebk_6?AxMG@0m`|0yXm!P-v`VWX z9Y-gQpQs?%GsA0KhKj0_t;!$BC;l#rCy`0gm;S2Vtd~bA&yI%)Ru7|>7g@?Q9;7r5 zUD#BVefbol`Gh$<#i?AFk7fb*NQgye1X}^@WY%L0l>JF&I^sJXj-kgh;FpfO;i!>^ z9pGD+t}9{EO{q!vncz}7mr!iUQC?Dh>Z0AJgi7_6R8PK~?$R7wLSKa?84fxbAU|!B z<{&Cz#Y`-V3s+HRJ|7zund_%rsFo<*tsQb6Nvx|C?l3G;Fv8?&3^%s5J6DfvPBnY0CbdA$pw?Myh=rN zb`VlIUp+Dby_<<17XqfIH_)VHffkXLUno|$7>O*QuqmS0K?Ugl5XJlo^Z}{`9#=(^ zsQfIe#%o#)DdEqRancqTP3l@D_OsYwUIiX6 zNXz4rvJ$APUNBe{H=r$79yLn5dsG*t^XY1e)IRx(s8cO%UNkR~A&4yJHNhViwxiMM zX$-C-4z?Q`CCfp#M@&t~eIOrSMMSCm+ImpyGePe2M2?S~mJ23RnV5_ylqzP^`@D*V;;O z^B@tt!!XXN2@XsKp;M!hUsb(N<)d4gY(#zI^gv2Qv#FS-FH#WAJ{HX($n8!v{gF`N zPFa!^?%t$C&<4}Dv1gKuvBt#GbXO#m!d{cs?L?ig>IFnGg~RkF6FFA`@x|IWjio#l z9TGvYr`8BLo%`Th%ih>^^2I;Wy>p^LmA$z&QXsuKZ35>)TFA@#%}a+2;W2<1NpOs` z)MR8Ex;X^^FD%##uP^~Lr-EJhYCx?q0c4Pl?zLzxN3+fZ;5#dm;2TFVgCYGs4gebr z4C+A~y01dR*JkZvpt`rw%_L%gYYYrB2cH74On|p?FeCuRasU$17&`|hu@#^!*y~La zP7a1dx%jjOkdMLeGXd~M4!|yhMCPR!;CDv=^6W&{E!YO}p^8$)+3|;@3-!zj1nxl7 zhsJ!iq+Um%`yd+Z#sq+R*M#nVGy`Z7&pu*{I*hR+8G!gz!aW#c1N*dJUAzR!Y!Acu z;_FZ%Lgp%A3ScZ!7Oc9QgW=O&g?u{#M%ZSi5_JsVo2yu_R}ljw-gKO;M<1Xwj%EVQ zq{#zEZ!GGEFqVBA@Bz9RAja@z6T@RU0NT&R-UCoEh6KR5RH@)s?(76u)K5cN&!9=9 zoj%f{<{gqvT)jUXfGo5F>UlIQ`GhXmK{5|9z>AT(aU7mvTjjhf@8j5;~X>m7+MLbJ8Sp;z01sqzQdY8%pZ*M`0Gpy)YZXPaFd} zc~O`HfVmlfchQ4`q*bl$wdnw6T&)AKh2~+FuFnBrkqOXdEe`XL2FfXwwOLEUZB*0UPYQZ$61kButW*3@N;vZeR z(cDZ;oIui#wOV0!mXH2w!fltgU+ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.csrf.CsrfException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.csrf.CsrfException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..16edb818a7ca2c515ca0363e0fd5b0f27480a41a GIT binary patch literal 11077 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 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.csrf.InvalidCsrfTokenException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.csrf.InvalidCsrfTokenException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..b7798b44b73ac581df264c2d6228cd6be8521549 GIT binary patch literal 11241 zcmeGiS&SS3)>w}%ObCE!APwh;5d%D}* zHG5bH5)!URgb+m#MGz4Z5eX$oKtv(|LPVHD{D1@qAp!Y8L6P`Ce1R0;y}G*km_6eB zh5e}Qse1kHdhgY%e(CRISsamLK~%TmFmnC+L}WMVbP!EiF?Cvz%Vw=U2H<%tn56#Dj6*}_2L4kI{r-(tpL^{49fZt8q;Fx^5RJy*of>}YAAK(z6&McA ziNSUp8pl>Y|2If|m)10%dtU$4wxj=g{C0t7^F@2gwmjRfTca(XxlO78pZ#d($yIwE z`KtiDJ^|DazkYk`(anc${AmtitPus%_P9rX_p`k{2d7{A)e^FFge-IHR!kXLKZ3Y+ zBGH{1^3VubU8Br)z0<(DT?e?e23(b)>JQydR>jPACdVS%0f?LLPl2d~A&)kx&j97R zxD|#GAT$&ZGtwnqYcbc`IpW6b&`fxVe1fcE_XTE>8q4 zzg76un@oJJ$$TF0~F zcqBkp&H$e%>GBb>rbb}_wK{<7a(2ap>rn%070A^HYN?r^)=I&wZt|H6G>XB~Iii`c zL`cU`MwZxN$jHe#y|v1>RaI`Lfgqw1Rh33AY^TfUNn@aBu-wjt16!yzMy~Vh=6KDn zfZs6Jsr2^tg&~)A4X{Y|AiG_R96%0TAAo$v4d4l^>D!)Fr#_8r7DQIljUDTx`Z-9Q zfX_3sF+V9uuaIw3lj2>H;?Xt~8@6BbK$X_{m_}7_Uk`S#CUGtbaBBwAAbD5| zJ4wGiO5>KtOm6l`ZVtaAZcYha8QE6Ii(HnRtOQQ-3_Kzk=x)P+=KX~2fCn?OE;GZi zBa$tqT=#LbIBdgVJNQ9e*p8kFYV?BbwM?sYJi*}^X~)K}a*ZLs4topO$jnF~S1T%6 z3gj8hQ8zyIFmjC(QJc}T;}@xuGRkWiQ2cqDDc!{k03$aDT%$q21ioSE!RBlI{59~L zHo6{)q9D>Xz0Uk8#oOQne$B;%W_u?fR-g4bkV%BcmoB8F5G06-AIUWaQx?k;dv} z@?ZFaIClHR+GG+NB0AxL|)DznH73A!{lQ*L9-e7v4yGC8WjoxZ#fS?A$qUN%t)`gMrcXU zR=R@dT>6Wov}X=VnBgHD8pJSY!VDuD5^{1*3pQ+!A%`UyUX}@gF1A7!Qh-JIcDLvZ zWG8fcOE3vlLg0X@0w2nm?39#hd5E!^)=Ww&+KnaxwWXBM;CmSh$sihN*sfoJ;NsS} zrzAj(Y)yV--_UZOGr74vZ%D-ruVbKsjOr~cr3RqvVUjB5o-M@-Qt~Z4eImn{wZ|yz zUx;%#^AcWkbun_^g0unV;Kj6GAL7=)ssJUPq)|l~dJ1MF9O%mSAZ~2eGqAC8Z%U5W z%OW9|xDdEdvd>74h81$hDH9%OyzD65*YZ>Vjw~Pt_d08%^yw29B>ECKS@%#r#rAzGCgjJK#&u75#=d1}ZG#|iMIRGPv z7AY!LwaC+$9Zy2BYyOntZIP;$QjX$zD|V--H(S|fvM7{|Y)s=YgF{M&Cpn8ad=iI; zARV+veZWL5)WbyoBU3rdWk-|-3LE?k?REMq|C;H0FSF*!L_Gzu74tk;D|ouLa!ys=~(uE7YgsSyzu6w@mby#w4pv( ztsbQnR=N}CpkoC^7gEU7-ege>^eUG`23`dQb|ehM44kNG0l!1G(>NQ(75!K_I|20+ zlqh;8RvvJwvb;Z$r9X=7S!@$l^ivhOGEiq?V+azpo|bCrnh;tYrA^4- zP^W4uxu5h=#!0`2O5# zToBNbH_~b_6dMb58#9pCj9+Ccj^$d`egTXXCrJ1p6Li1c{~7IX@lRB&#pVwV|O z^ENLGGh&jLIVZ{1c)fsK@>Yu76$*0!XvY}vxjn~c&Mom(zJb0|fCQWoXTPU`iX07g z8Lq#C!^=3du_7)BMdQ1suT)aX3e?W82W%v@D6~rm6-}6lJU)OMngzrr)qayyc zIHe-=TmRQ){=6d2a;P5RefadGsi=cE(E?}Rw|u7oeRjA_rIxr`69-x7G@gYv4jkBIm0g}=wV3d? zIAIt{%W&w#pDDHIj$k{0Lm3Rv{YiH^k~1Ev`SBe1hG~~Ij6Q6YpStwD625dt=A|5$ zBDpZKtH5|^{mFy&h*T=vw`69D^>kiya4kHQcz90fX(tCLZrfxzP*+$96C0C@D>Yfn zM~@`4cV0oYM&WFwa5A!~XquDC+f5&h(FB_$;WP6R=J$f3%^LllTle|RNP{|)1{<0O zJQ?m@6uj0-s;$+{nS-IU0f&wFv%osIc0CU3QUD+w`8ed%ODI}BvfO(P7ndg0Jn)UpiERNG? zRQyB%oDc=uibI)^RpRTW%C24df(C&e1S$E2$-6A2q`EX_L1;2{4CwKTfcfcFThJ`f zD$@F6r6Q7T-XhfxEydd1<1qpT({5P3@MX@|TM`X%{+8<_JSh zbG2jXTKXWaA1MObfk5OK4*Ytg2ncSSaeWX6-T==5p#du(9=}qS3U%cd3|7sJVaquU z3-`FU54hG=Dm(GZN zAik(lMB)0i{i3#Kirhzl91l5d1qN4{nN<|}Yjy}dr;7Rw`!%`N(_d|DDtbrJPcDhn z7Y%FiO9a?}Gqby`3V(GU*HAa?vt&trFog>I1u)G&KAhj}@f%4@7PRQbuKJAW{G>h4y-8Z zvAGuULkix1mU;jk@^3D+&q&_jCd#Y9mze0Fy0Q`KU}SUQytY<~o6A7(LxywCO|Uu} zGurIOugAo93OsZRmsQkz<_D4{S_+qG`XGg>ImJ~of_giR!x^PiOsC?Gi;+8|3Kqch zNuHSmW87k7eRfJ|rF7h+br(?QXV%h(k&jVEmSWEpKq9g=X=BMu1vZE)L(P!!cJAfZ RHokKB#b^Hv=gygM=6`1(@xuTB literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.csrf.MissingCsrfTokenException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.csrf.MissingCsrfTokenException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..171eee2bd8e7c029f5d8c49f738fd00b18ff2b26 GIT binary patch literal 11227 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 OzVOVSVBa|(&;JkJn(NX4 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.firewall.RequestRejectedException.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.firewall.RequestRejectedException.serialized new file mode 100644 index 0000000000000000000000000000000000000000..47de867dfe1d93043526ec3e4d4551e4226b635a GIT binary patch literal 11020 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 V&b@Ja<2%Q{_WB=T-#HV^{10eQnGyg1 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.savedrequest.DefaultSavedRequest.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.savedrequest.DefaultSavedRequest.serialized new file mode 100644 index 0000000000000000000000000000000000000000..c35bb07a8605dd2be856147bc844caf497d69c96 GIT binary patch literal 1787 zcmb7E%Z}qj6s?Y(XCuu6&A@`;6Vhw}jll2sJMp8~OikkMgh|I4yV9KoX(T=a3H|^+ z0+<=wZ0J}mPsw&g6K0F|HpH*as)O!WXw;PTMOlYcOM~8!87DAVqf4S&31P%b~PTADC^H)tb|GeqOHW^|bTqoGjPvo4CG#TV!yy_U;8) zZTCE|j>U&Z2)*$$MFYD?>>$t+iu!?W=OuoqIQsnxt8Pkh=XJzxnpz zlOF`s(a~sCk-1K0w5lyMq>H+27S(FL+kEgLN9VOmsEqEyuM17Os$(i<$UEwRt;b2| zQPh>Np}A1j%p4P51KW&JiUgca;lBvrdFW&~4^>x*b})hycv7n9vaH=sHn1kSmpE<+ zp^6V#hjC%$rtanu^bk)2SHe0>o`rgV9r&C?zN#8oC`0CM?&-3ANZQH-Du$91E1Kto z1voi{WlF3MRZX=#(StNQgou175sJ$iASu{^1!c1YhfW5u16R@#)i5UzK2&4ZFm*}& zk10eazz{gH9mmQ7#WHNxykVq%q(G!@U`xkX0c+P!)EI*;$Fk>4{4Cq;4&4a68v&TS zd&8v5_rQxk?>+hAoxgq)P|rex1+Y7-<|S9_c1`Q6R)-^4N#&XenJ0y%Fknk*OU@2U zqM6mp&9%Dw7o=X2R_wdY-*8dXF(wotxbL^mW0w2zzRUMo4=?6M@HssNCAft06Y?)F z)xP#3HGkS?Vqm5?2~EkI0`FoN?XG*adfP@5Xs&82XHi#MW_GL4V{m1}L?5WBW+y|*y zn4QaEYQ~Y07VO*#u8$9K9Z0CfUY-NnLpf3k*U$r)p_fgtC#MHLc1$b@PbDS_y+{pB z3L~atCAZTkhFVyXaz*8R77}C_PO`w5czJ{Y9zjz<{@uhfLk;e6s+ypEL6xANuIGhQ XcE;2+Dcs34096gcNvLUFFm?X{R0i=q literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.savedrequest.SavedCookie.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.savedrequest.SavedCookie.serialized new file mode 100644 index 0000000000000000000000000000000000000000..16a8258f007c5edf03bbffc1677c1858dbabb208 GIT binary patch literal 250 zcmXv|OHKko6nqdE2pbc3UZPnWS3&|d&KRZRuXhi4jxE#OL-(5jH=Kw=cp>lLfLg2i zq*8VGhHeaS=|e7J8H~%{0$SSouojV)RWST1wzLotYgz@`R1{gLqZoU?HUuZMi#GHn zBo=qqIcZ2vU6RnNDMaI43caPbmK-yjryp43ut1j&6(2XRQb<5ee>P$;Dn@f4a J(7Aue#y>$WN(cY| literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.savedrequest.SimpleSavedRequest.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.savedrequest.SimpleSavedRequest.serialized new file mode 100644 index 0000000000000000000000000000000000000000..58449b0e225938c89ba127cfa45d72b240900838 GIT binary patch literal 1022 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

!t&ypeegDC{?7ohYB4WoII3I3=MmUayM zXZfRX7|u$PSLvtLu;J-mcsb*RrAR=Ro`!iPPv_xG<|3TH!SDY(acGXeW7B2TxUm?R zB_7x+v7)J`=NrTyQt{+rS)(VH9q>GlpWt!SZAvEVXG#3!Jr?DA`LB-{?4|=fN zeL_O82&K>Ql}RYZJw`TVyJA#IXLMPp0^hTihER`TMwa23E0Ba_S8|LcS1L#nk^iG}7 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.session.HttpSessionCreatedEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.session.HttpSessionCreatedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..bf05c7183134ae7534d9540df8e9b025d8e2087d GIT binary patch literal 354 zcmZ4UmVvdnh`}MhC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(c(DK5^;&(rfL zDJcksu$_xi6H8K4T+34PN@mu6liQU%*_(-hp`wVv4zF>^`9-O4^N`H~nh!Rt@Tss^ z-Ksa+NjA(esko#lF}Vb4DA=$Coxb0XSQl_Y4Aa5pvgG`{lGKV4J;#EAoXq6J5|}%Z zT3*L@xSxH<#K7pqz*&-+n_66wm|Fn!plDWNS)yKPNoI~7*b@JwtkmQZpI%NsiQL*1 QAgc-(7#OE53bE+{0KBe;S^xk5 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.session.HttpSessionIdChangedEvent.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.session.HttpSessionIdChangedEvent.serialized new file mode 100644 index 0000000000000000000000000000000000000000..279bb818bc01c5812a2aba86b0dfa51e324b1bcd GIT binary patch literal 421 zcmZ4UmVvdnh`}kpC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(c(DK5^;&(rfL zDJcksusu_pGZORCQ&U{aQu9hSbjc=d7P@N9#K7dkz>}9+4p&veAmo#kSeB@t15~Xa zTmrJi8mJ~eCk3vikfDx&v7(5<9K$1FQ5;%1TWx@#*FClgO=I0kW!qfq`+_ OqL?735(Y*?5C#C|2b%)` literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse.serialized new file mode 100644 index 0000000000000000000000000000000000000000..f2e8085330bec37cb385e2e167f6b0a967724da1 GIT binary patch literal 783 zcmZ4UmVvdnh`}enC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGW8sRtkk@c%;dz9{36HV;?$y&%>2Bd)Z&8tyy8@rrk>=qc{ivN*GK**6E{Jrw_AEA7~W_gRRpCTBq++2{ghQWEQf$E{P?Hg$#8Jj6MvU z#hK}Oi6x~)sZar~QlOhW67y1WAR-k-49*mI5$3alCo(rQy(&4+1hSMbIVTes2q0&8 z1^fGfZLTN)2BtA-Hi3P2sd1aV#<2zZK$D{xSd%~=E@5DbcB&{SEdtSNK8ycg;|O`c z#K2Hdz`(%JR;^T0S&*t^r34Epz4X))B^{;YjKrLr)Vy?%RE9xWdbxXGR=#tUW0gy2 zac)&WLAI_}n5S1zPKUuePQJc~6nK7StHOo#LBb$|V+i`+n=54-Bg2Cg)VQvhV)WS@CLN$e~+#zBkZ} nRalsmJ7L~>^`ATQ&vCQ&pL)wQnbXifr{hq2x2u6-yLAr$lY1K4 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.AuthenticatorAttachment.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.AuthenticatorAttachment.serialized new file mode 100644 index 0000000000000000000000000000000000000000..449d5b9a9814420db1f69c3f4be1de49b487a2a1 GIT binary patch literal 130 zcmWlSy$!-J6b2tikq{+=I2{dD3c7Sm@WpZ*ga7pWoWM>rjKl!!KvJA^SKa$ZO<}qw zwyI=y(T}OSJ!DKA{-WA27XSbN literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.AuthenticatorTransport.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.AuthenticatorTransport.serialized new file mode 100644 index 0000000000000000000000000000000000000000..183124aa4e607bd6bfea05073e6e1951ac2c9993 GIT binary patch literal 124 zcmZ4UmVvdnh`}YlC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGW8sRtkk@c%;dz9{GyPe#Ju8y{GyUgoIFYI*0@VCF);ctu$Cp}l%|$22>E0smL=-v WB<7{-2bX{>u&yX5VPGyTP67aZ3oTy& literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.Bytes.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.Bytes.serialized new file mode 100644 index 0000000000000000000000000000000000000000..3f268bddcd4691a3b16f695bb36970d33abfe294 GIT binary patch literal 140 zcmZ4UmVvdnh`~6&C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGWDD)OHzw3HEy%lIJQ8aiGeYifi(#vRKma%?Nm`vS_Gokd=~$~#u4&>iGiV_fPsNQ n!BSzZ*$cs6brSAf##2h7eke^p+n@hinQJTKoP95L9=Q(yPv156 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput$CredProtect.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput$CredProtect.serialized new file mode 100644 index 0000000000000000000000000000000000000000..0222d302d6c70ca486e5530b9f0697b4089e9867 GIT binary patch literal 507 zcmZ4UmVvdnh#@1tC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGWDE`Qd0to@=H>aOB{iMsd*)t$%!SI`FXAtC8>GEKt{21P9{*;Gq0euLng-aN!eSj|3M{}`0 z%*FaZ*MczE#ri-O>mxaVL>H@|yUDtufU&NKp^{!fhT#APC=g;`s3>CK%StRu)XPcC oOV@MFE6qigEGS`+3JrD*iVt%Q@^tfbb`0_K_lx%r08x%U0N0thNdN!< literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.serialized new file mode 100644 index 0000000000000000000000000000000000000000..0b0b44e588ba52ef7628208ebbdefa770e256110 GIT binary patch literal 733 zcmchT&q~8U5Qis~URA`CH=!3jY#zW<2oVCI4fPKwVp-R1x;5PmlZj0b#EW;I!jrG2 z_z3Pw)lg3!?830aZx_Cq@8uP`CBlWljFkl{osE&tH@p6Cf?ERqwopacgdTdZgH;%K!^My96C^fkf zO#<8E$L4oTaJMo+z2%v#G4CFsS(UP11Hj^J0n`ZVQ+~@?&UMBjUCr8$1;NhAQ8XN# TM#K0p4uj)(&>sy_cLm7@6-y8r literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredentialPropertiesOutput$ExtensionOutput.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredentialPropertiesOutput$ExtensionOutput.serialized new file mode 100644 index 0000000000000000000000000000000000000000..2ff01fc9d19abc205bb2927323a61a932205a7f8 GIT binary patch literal 115 zcmZ4UmVvdnh#?}sC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGWDE`Qd3g%N-`630*dkrQj1D5Q;YpeOA1O$R9q`cQuB&4^Yb8ldwY#P-vrHsnHU(O L7?_H(D+(9^Dt9X> literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredentialPropertiesOutput.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.CredentialPropertiesOutput.serialized new file mode 100644 index 0000000000000000000000000000000000000000..78ddecc04253cac26a4cf70c69596d048012bfc7 GIT binary patch literal 306 zcmZ4UmVvdnh`}SjC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGWDE`Qd3g%N-`630*dkrQj1D5Q;YpeOA1O$E>CpN|LC__mWhGUhk-30ELOr0pQyUgOUMHMz7Xv!qh5JT(b~6H7}n^7Il5 zGW9%jb4yDSlX6lWfkLTyC7H>IC7Jnot`#MzdBs3Rv2#u)P}noCptR)qgX^vTSRO<$ zF);ZsaHqnQc&3yv2>E0smL=-vB<7{-2bX{>xAtLR%>?N}mGn=_N=+`Yt|%yB;7l$` yO$jKKT88K7{=c_Y(X6Sw~2#;bNT_!rHVu0KtTuTQ6as?GxeGa$(#1Nxd?s;2M0k1 zKZCPN7eQ2T=;mZUfP$EuD7tw=NO%&S_j!J~euauw5M?SMS}HD*14Vmmkf|f0S!@)? zAsMi?Th!p8AXIYF==BWJcFO8jVgh+gk!NBvM<%pA=#7+Hxgn%M9kibJ-_I`}eego4 z@M1=m+`~N$JBVw2S0=v_6@et zL0I@zdk5W@1tC-lu759om>^8YigmVCCbf+{UqE))$aMHMz7Xv!qh5JT(b~6H7}n^7Il5 zGW9%jb4yDSlX6lWfkLTyC7H>IC7Jnot`#MzdBs3Rv2#u)P}sk;q@c8eqx#L%V ziZd}V`Y^EPLj+401bng*%M$fVOEPoxeKLzntSbtDE|CHW>VX9H9E*w)D~naY6iC6% zB{uuccCcLnniI{ym{`If91SrdCowNw-#;lUH5p_`X%Rzcv=3<>BE}!qDnIl4_s#s> zz{J2%QNX~!zzB4%2Wj>=7p10v!YeT+peVl}wWuUBwHOjgmnXXCfArfd3l1bUNFbFk zM1lOFj}|fdu!zwIMhXanBSs$>G5Wa8SAj<{9 literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity.serialized new file mode 100644 index 0000000000000000000000000000000000000000..f0703c6b92f66fc4089155b7d0fbb7bd20b69546 GIT binary patch literal 361 zcmZ4UmVvdnh#@4uC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGW9%jb4yDSlX6l6N|SOjlf6?bor_XaQu9hO6LUg~Q;S@ItdhzL?tc7_EaVEA7?^z+ zxKlEV3vv=G{eY&IFbMf%C6*=X=OpH(>j#&BthM%GV9HD>VKDUpIz=DNDf%#{=mVVr z!eFQ91D&GpR9TW*4Ajh$2Q;{lp^kyEqM(F literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.PublicKeyCredential.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.PublicKeyCredential.serialized new file mode 100644 index 0000000000000000000000000000000000000000..5854d4bed3ca1134d5df858f69820f299d9af0e9 GIT binary patch literal 2288 zcmb_eZA=?w9Dk@lHx^Mw0+|sr2!xS9uLIUqFu~H*LSKLt+7&v(o*vis==JV+?m1|g zqRwn%kp$h0xR~LlGwuWNCB6k_rtZUL%*1TQL{xl9oF8VEpo=-~Z+J|35c%8RRGk*d!D+DKg^4XavCoHzc9BN#R%(@i=7~;==TSDvpUJDD$Qc zH7xL~n@iad*T;#Nhr%zVM~+-vpYJn(Tn{LwTj@Ltv4m_GLpGKm-xxSOgo(*uVwRa0 z!a^U7i6Kmk?e;*89#F&zJb`p1F()d#B>Ff-6|jPV|IR50%ndxnuHm<<;wSb|<@q&Ha5ltoBY<((85x9|_cjMda?{y`3(uRg62g+AYnsLI<`ww(xas z=p}im)8~lR3obnMe$%EiM;T2|Ibqr!FffFgH3^Y%>Pqp|+%;W4F6Y~pHS)TDd%@Jm ziQ{Kp?%@B5p0BO<%nU5`X215t+*oiTI3N1>U}a(6h5`Tl^siOcv6hL^N!!VFM+cu@ zdaUrp52{`zhTgd_No!XWDjBW&YON=G&lnq-oNF+EY!4`an0f%|hIBE|O4k-vw)Jfg z!|Mxc6+S9LtRk%^kezudA!vbOpOBtOB4F1S*cF8&q2&(w$*25VdoJNGG;QanZxU*T ztFygLGleufI&M(u`+ibd`*xSnmRiY}WgjJb-|7w*0|nm-o9^~Lw}4ekn!|NO@EEBk8C9^QFn z=IN3NwuHC?G7dS-4#P%c@41D_@Bgmtty+jT7a!_+cX;O}iN`XS6 zQ79!&BO#$siB6*zN)3%hD402sh)`VdGR5S5|Ns8?N>32+1WdRhr+ZvjPHtY%WtMlv zlJ2prFDwl7JTp~M9~T^*a!a4|P20*&uplYe9COg3_9*kR!s6(PwHn$!-M6u$LD&Gz znnUO_B%b`jlW#ncXjNrAk$92_ka=+fq9~~2aaW*+&|oamRZ4ucY?5XuzpF6d3?Wq^ z{SWgQ35plE45GKPAj(c}*UyH!uMda@p;?HS$~=NLn<#K!sJHae^jck$dC&j~8~{27 zI*$7vV{gUg)MoE4&b)T+-E6z>UD4Cf{^jHO)xGo)Q^V?NSUu_5cAAN)t)sKS*27pB zLgNrdnk%!ke3c}IWXEV_&PU`j)#kEPt}OGN^GR);`CLl1$WaW4tWw*T0XKdnZ&N2n s(x`4kW`z*;J+mN%$0`&4Cw^M|7`|w`bAKqWKoRPdQk0kPt!Ie^pQ1YZK>z>% literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions.serialized new file mode 100644 index 0000000000000000000000000000000000000000..40415ad5dfe779d59650f4b9421c6e5c25e34b24 GIT binary patch literal 1833 zcmbtVT}TvB6uvXAziGMsP-xLoJ@imB4^dePl|R_#f|A=0yBOSEuj|z8%=O+g?QD<~ z7(@>hRP-X@gOHHD2ub;1Jw)`75lU8&v~T&6FTF_7&%I-sg+)UHGsDc>d(L;h^PO)7 zKcIZhP_xZa8mG*#QXLGZNycWUG)|HZGa##FNJ5_2flf=q)X-X-glQz3NVblVc4C2n z&BLV2Asm`17`DYHpVq`?ul3ESs33yMv1!_wpG!HQ^2kYi8tV=irXDdkR7X&8vQw-h zR*C@H84-B&-|*=3cyxh9o*o`u;L&TdK)8reLR=sgmk0nweFixo%9FJuu`HosAyS=b zh}U6@Iv{fd7!9`rS~*vNiLI303X(YStq_bfu^j-kdhWA)*6SVSg%k_Q9ZuL$!iorp710yDA?>w5#D%wXU|m?9b}!xpDD1JRiki@M)RWb*@% zrK0bRKDk&zhfxt|j)xuos`4~6etvl0)+c?sij+9YPe@q-QscEQb(p*leq1|SP!!Fn z2zeZa_Jr>4nht*IUe|bO$90I!Zn`;k#s0jxv8EmW&KqDysG&UU~GX-14;jbZJPvJV{GdO--Vib`qPNHV3i2%a literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialType.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialType.serialized new file mode 100644 index 0000000000000000000000000000000000000000..efc9d8e3ee6ffdf41e7ef918aa0b43897ef12e89 GIT binary patch literal 132 zcmZ4UmVvdnh`}|#C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGW7yVlX5bXy;Ccli&9fk^GY%kb3!T$QWNX`JM5CZo6p3+=)=HTmY7qTTEZaYla*MO csGpOVm#!aN0MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGW9}>Q;Whj-tF_ctg;o!i;xBrT1SyKrgY4F_E{(0Uy9g~PlVn<_olVl2iA_8d zi#HE?5kye%EcIAH5Km$cB1ruM6i+?q$%Dlo;G3-LwgtB|kV*3L-uJ%u%{NP5p;-zT z4aBr5L&5y%DM2c9J`l5}q;4dbO3Zoc=$l09x^L>0`ii+kvA{R4P$#G3dZ~wB7P^LW zE5C0Wkg=gvVb8oKsgTF<`?kk1E1ArtGh#aNKr}&$?f6lZ4Tw(*4wM!ds5JhI|mEU6o*LaKpiC~ z1W7b{ytsP!;R<=&1eqdaCA&%E5CAgsS-oOWzM=4LAZTh8ty*G^vr5QmK@eY2PphAX z5eYAYSEm#Kr<;`9v8hdpb529^N`uUf5@q{EvK5hO?0Z_D$%o9zXqhI%0_s&aE|_+&5?qwEcDDOy=V-68gFHt$V#?cj=P>kXottT<^0pYx{mI94y@Fzpp0Nk34vH VFZg+M&vNF`tF;%eKkD(i{{Z(Hr?&tA literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken.serialized new file mode 100644 index 0000000000000000000000000000000000000000..36ae5f437c29b8ca554c8e197a1bc36d1dfb1d05 GIT binary patch literal 4101 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 b{4W_ANjhg0a&}v%E1)O|qh^HZe Spring Security Authorization Tag Library - 6.5 + 7.0 security http://www.springframework.org/security/tags From cb0fdef2363bf997671f7644f0034515936ec30a Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 2 May 2025 17:50:24 -0500 Subject: [PATCH 003/149] Remove MediaType.APPLICATION_JSON_UTF Closes gh-17050 --- ...ultOAuth2TokenRequestHeadersConverter.java | 6 +-- ...orizationCodeTokenResponseClientTests.java | 3 +- ...ntCredentialsTokenResponseClientTests.java | 3 +- ...aultJwtBearerTokenResponseClientTests.java | 3 +- ...faultPasswordTokenResponseClientTests.java | 3 +- ...tRefreshTokenTokenResponseClientTests.java | 3 +- ...earerGrantRequestEntityConverterTests.java | 3 +- ...nCodeGrantRequestEntityConverterTests.java | 5 ++- ...tialsGrantRequestEntityConverterTests.java | 5 ++- ...swordGrantRequestEntityConverterTests.java | 3 +- ...TokenGrantRequestEntityConverterTests.java | 3 +- .../security/http/MediaTypes.java | 39 +++++++++++++++++++ 12 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 web/src/main/java/org/springframework/security/http/MediaTypes.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java index 92d5d5e1ec..c7bb26f91f 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java @@ -24,6 +24,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -41,9 +42,6 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod; public final class DefaultOAuth2TokenRequestHeadersConverter implements Converter { - private static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON, - StandardCharsets.UTF_8); - private static final MediaType APPLICATION_FORM_URLENCODED_UTF8 = new MediaType( MediaType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8); @@ -101,7 +99,7 @@ public final class DefaultOAuth2TokenRequestHeadersConverter DefaultOAuth2TokenRequestHeadersConverter withCharsetUtf8() { DefaultOAuth2TokenRequestHeadersConverter converter = new DefaultOAuth2TokenRequestHeadersConverter<>(); - converter.accept = List.of(APPLICATION_JSON_UTF8); + converter.accept = List.of(MediaTypes.APPLICATION_JSON_UTF8); converter.contentType = APPLICATION_FORM_URLENCODED_UTF8; return converter; } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java index 4aed182e02..e285c61ff6 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java @@ -33,6 +33,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -118,7 +119,7 @@ public class DefaultAuthorizationCodeTokenResponseClientTests { 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_UTF8_VALUE); + assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.APPLICATION_JSON_UTF8_VALUE); assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); String formParameters = recordedRequest.getBody().readUtf8(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java index cc8a62f8aa..d175bcf22b 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java @@ -33,6 +33,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -121,7 +122,7 @@ public class DefaultClientCredentialsTokenResponseClientTests { 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_UTF8_VALUE); + assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.APPLICATION_JSON_UTF8_VALUE); assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); String formParameters = recordedRequest.getBody().readUtf8(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java index 555d4bbf66..a00a9b0090 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -115,7 +116,7 @@ public class DefaultJwtBearerTokenResponseClientTests { 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_UTF8_VALUE); + assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.APPLICATION_JSON_UTF8_VALUE); assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); String formParameters = recordedRequest.getBody().readUtf8(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java index 5c0233efc9..a33b234e64 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java @@ -33,6 +33,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -115,7 +116,7 @@ public class DefaultPasswordTokenResponseClientTests { 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_UTF8_VALUE); + assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.APPLICATION_JSON_UTF8_VALUE); assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); String formParameters = recordedRequest.getBody().readUtf8(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java index d020ce8047..61c4f2d2de 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -117,7 +118,7 @@ public class DefaultRefreshTokenTokenResponseClientTests { 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_UTF8_VALUE); + assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.APPLICATION_JSON_UTF8_VALUE); assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java index c284d2d179..88d06285b2 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java @@ -25,6 +25,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -134,7 +135,7 @@ public class JwtBearerGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaType.valueOf(MediaType.APPLICATION_JSON_UTF8_VALUE)); + assertThat(headers.getAccept()).contains(MediaType.valueOf(MediaTypes.APPLICATION_JSON_UTF8_VALUE)); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java index 9c4fc4d88f..ac824a681d 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java @@ -28,6 +28,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -138,7 +139,7 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0JTNE"); @@ -177,7 +178,7 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isNull(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java index f988edcd3c..af25ccb9f1 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java @@ -30,6 +30,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -123,7 +124,7 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); @@ -152,7 +153,7 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); String urlEncodedClientCredential = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters, diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java index d884559f73..5a6a8811e8 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java @@ -25,6 +25,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -118,7 +119,7 @@ public class OAuth2PasswordGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java index 1278e92e47..fee0e2b02c 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java @@ -27,6 +27,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; +import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -130,7 +131,7 @@ public class OAuth2RefreshTokenGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); diff --git a/web/src/main/java/org/springframework/security/http/MediaTypes.java b/web/src/main/java/org/springframework/security/http/MediaTypes.java new file mode 100644 index 0000000000..96b6c2d989 --- /dev/null +++ b/web/src/main/java/org/springframework/security/http/MediaTypes.java @@ -0,0 +1,39 @@ +/* + * 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.http; + +import java.nio.charset.StandardCharsets; + +import org.springframework.http.MediaType; + +/** + * This is a placeholder to allow an incremental update to Spring Framework 7.0. + * + * @deprecated For removal + */ +@Deprecated(since = "7.0.0", forRemoval = true) +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON, + StandardCharsets.UTF_8); + + public static final String APPLICATION_JSON_UTF8_VALUE = APPLICATION_JSON_UTF8.toString(); + + private MediaTypes() { + } + +} From 222faae1cb5e7f5bbea7eeb8c517e7159dedf189 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Fri, 2 May 2025 17:31:17 -0500 Subject: [PATCH 004/149] Add junit-jupiter-engine This fixes some of the compatability problems that can happen with newer versions of junit --- config/spring-security-config.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/config/spring-security-config.gradle b/config/spring-security-config.gradle index 90e7b88312..83f91c9bf0 100644 --- a/config/spring-security-config.gradle +++ b/config/spring-security-config.gradle @@ -127,6 +127,7 @@ dependencies { testRuntimeOnly 'org.hsqldb:hsqldb' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' } def rncToXsd = tasks.named('rncToXsd', RncToXsd) From 38a9aa1da999836d7f384eccc59afdee6042ceb9 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 10:48:32 -0500 Subject: [PATCH 005/149] Remove Deprecated PathMatchConfigurer usage Closes gh-17052 --- .../web/builders/WebSecurityTests.java | 57 ---------- .../configurers/AuthorizeRequestsTests.java | 103 ------------------ .../HttpSecurityRequestMatchersTests.java | 61 ----------- .../HttpSecuritySecurityMatchersTests.java | 74 ------------- .../UrlAuthorizationConfigurerTests.java | 58 ---------- .../config/http/InterceptUrlConfigTests.java | 6 - .../web/AuthorizeHttpRequestsDslTests.kt | 28 ----- .../web/AuthorizeRequestsDslTests.kt | 28 ----- .../InterceptUrlConfigTests-MvcMatchers.xml | 2 +- ...gTests-MvcMatchersAuthorizationManager.xml | 2 +- ...tUrlConfigTests-MvcMatchersServletPath.xml | 2 +- ...atchersServletPathAuthorizationManager.xml | 2 +- 12 files changed, 4 insertions(+), 419 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java index 3219676502..a8d18adf4a 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java @@ -47,8 +47,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import static org.assertj.core.api.Assertions.assertThat; @@ -84,26 +82,6 @@ public class WebSecurityTests { } } - @Test - public void ignoringMvcMatcher() throws Exception { - loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class); - this.request.setRequestURI("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setRequestURI("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setRequestURI("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setRequestURI("/other"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - @Test public void requestRejectedHandlerInvoked() throws ServletException, IOException { loadConfig(DefaultConfig.class); @@ -132,30 +110,6 @@ public class WebSecurityTests { assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST); } - @Test - public void ignoringMvcMatcherServletPath() throws Exception { - loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setServletPath("/other"); - this.request.setRequestURI("/other/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - public void loadConfig(Class... configs) { this.context = new AnnotationConfigWebApplicationContext(); this.context.register(configs); @@ -246,17 +200,6 @@ public class WebSecurityTests { } - @Configuration - static class LegacyMvcMatchingConfig implements WebMvcConfigurer { - - @Override - public void configurePathMatch(PathMatchConfigurer configurer) { - configurer.setUseSuffixPatternMatch(true); - configurer.setUseTrailingSlashMatch(true); - } - - } - @Configuration @EnableWebSecurity static class RequestRejectedHandlerConfig { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java index 65d7c13bea..4bf3d4ef78 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java @@ -48,8 +48,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import static org.assertj.core.api.Assertions.assertThat; @@ -160,67 +158,6 @@ public class AuthorizeRequestsTests { assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } - @Test - public void mvcMatcher() throws Exception { - loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class); - this.request.setRequestURI("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setRequestURI("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - - @Test - public void requestWhenMvcMatcherDenyAllThenRespondsWithUnauthorized() throws Exception { - loadConfig(MvcMatcherInLambdaConfig.class, LegacyMvcMatchingConfig.class); - this.request.setRequestURI("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setRequestURI("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - - @Test - public void requestWhenMvcMatcherServletPathDenyAllThenMatchesOnServletPath() throws Exception { - loadConfig(MvcMatcherServletPathInLambdaConfig.class, LegacyMvcMatchingConfig.class); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/foo"); - this.request.setRequestURI("/foo/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setServletPath("/"); - this.request.setRequestURI("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - @Test public void mvcMatcherPathVariables() throws Exception { loadConfig(MvcMatcherPathVariablesConfig.class); @@ -245,35 +182,6 @@ public class AuthorizeRequestsTests { assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); } - @Test - public void mvcMatcherServletPath() throws Exception { - loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/foo"); - this.request.setRequestURI("/foo/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setServletPath("/"); - this.request.setRequestURI("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - public void loadConfig(Class... configs) { this.context = new AnnotationConfigWebApplicationContext(); this.context.register(configs); @@ -639,15 +547,4 @@ public class AuthorizeRequestsTests { } - @Configuration - static class LegacyMvcMatchingConfig implements WebMvcConfigurer { - - @Override - public void configurePathMatch(PathMatchConfigurer configurer) { - configurer.setUseSuffixPatternMatch(true); - configurer.setUseTrailingSlashMatch(true); - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java index 9fed7f0c4a..52f97c4a3e 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java @@ -41,8 +41,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import static org.assertj.core.api.Assertions.assertThat; @@ -80,60 +78,12 @@ public class HttpSecurityRequestMatchersTests { } } - @Test - public void mvcMatcher() throws Exception { - loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class); - this.request.setServletPath("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - @Test public void mvcMatcherGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() { loadConfig(MvcMatcherConfig.class); assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty(); } - @Test - public void requestMatchersMvcMatcher() throws Exception { - loadConfig(RequestMatchersMvcMatcherConfig.class, LegacyMvcMatchingConfig.class); - this.request.setServletPath("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - - @Test - public void requestMatchersWhenMvcMatcherInLambdaThenPathIsSecured() throws Exception { - loadConfig(RequestMatchersMvcMatcherInLambdaConfig.class, LegacyMvcMatchingConfig.class); - this.request.setServletPath("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - @Test public void requestMatchersMvcMatcherServletPath() throws Exception { loadConfig(RequestMatchersMvcMatcherServeltPathConfig.class); @@ -491,15 +441,4 @@ public class HttpSecurityRequestMatchersTests { } - @Configuration - static class LegacyMvcMatchingConfig implements WebMvcConfigurer { - - @Override - public void configurePathMatch(PathMatchConfigurer configurer) { - configurer.setUseSuffixPatternMatch(true); - configurer.setUseTrailingSlashMatch(true); - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java index e5c080aefe..3f70043e67 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java @@ -16,8 +16,6 @@ package org.springframework.security.config.annotation.web.configurers; -import java.util.List; - import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -38,19 +36,14 @@ 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.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.servlet.MockServletContext; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import static org.assertj.core.api.Assertions.assertThat; @@ -88,68 +81,12 @@ public class HttpSecuritySecurityMatchersTests { } } - @Test - public void securityMatcherWhenMvcThenMvcMatcher() throws Exception { - loadConfig(SecurityMatcherMvcConfig.class, LegacyMvcMatchingConfig.class); - this.request.setServletPath("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - @Test public void securityMatcherWhenMvcMatcherAndGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() { loadConfig(SecurityMatcherMvcConfig.class); assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty(); } - @Test - public void securityMatchersWhenMvcThenMvcMatcher() throws Exception { - loadConfig(SecurityMatchersMvcMatcherConfig.class, LegacyMvcMatchingConfig.class); - this.request.setServletPath("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - List requestMatchers = this.springSecurityFilterChain.getFilterChains() - .stream() - .map((chain) -> ((DefaultSecurityFilterChain) chain).getRequestMatcher()) - .map((matcher) -> ReflectionTestUtils.getField(matcher, "requestMatchers")) - .map((matchers) -> (List) matchers) - .findFirst() - .get(); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - assertThat(requestMatchers).hasOnlyElementsOfType(MvcRequestMatcher.class); - } - - @Test - public void securityMatchersWhenMvcMatcherInLambdaThenPathIsSecured() throws Exception { - loadConfig(SecurityMatchersMvcMatcherInLambdaConfig.class, LegacyMvcMatchingConfig.class); - this.request.setServletPath("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - @Test public void securityMatchersMvcMatcherServletPath() throws Exception { loadConfig(SecurityMatchersMvcMatcherServletPathConfig.class); @@ -501,15 +438,4 @@ public class HttpSecuritySecurityMatchersTests { } - @Configuration - static class LegacyMvcMatchingConfig implements WebMvcConfigurer { - - @Override - public void configurePathMatch(PathMatchConfigurer configurer) { - configurer.setUseSuffixPatternMatch(true); - configurer.setUseTrailingSlashMatch(true); - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java index f98c86bbf2..fe93d6bb64 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java @@ -46,8 +46,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import static org.assertj.core.api.Assertions.assertThat; @@ -85,51 +83,6 @@ public class UrlAuthorizationConfigurerTests { } } - @Test - public void mvcMatcher() throws Exception { - loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class); - this.request.setRequestURI("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setRequestURI("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - - @Test - public void mvcMatcherServletPath() throws Exception { - loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath("/foo"); - this.request.setRequestURI("/foo/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setServletPath("/"); - this.request.setRequestURI("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - @Test public void anonymousUrlAuthorization() { loadConfig(AnonymousUrlAuthorizationConfig.class); @@ -258,17 +211,6 @@ public class UrlAuthorizationConfigurerTests { } - @Configuration - static class LegacyMvcMatchingConfig implements WebMvcConfigurer { - - @Override - public void configurePathMatch(PathMatchConfigurer configurer) { - configurer.setUseSuffixPatternMatch(true); - configurer.setUseTrailingSlashMatch(true); - } - - } - @EnableWebSecurity @Configuration @EnableWebMvc diff --git a/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java b/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java index bdcef2a2a2..f0f983022f 100644 --- a/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java @@ -254,8 +254,6 @@ public class InterceptUrlConfigTests { public void requestWhenUsingMvcMatchersThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("MvcMatchers")).autowire(); this.mvc.perform(get("/path")).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/path.html")).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/path/")).andExpect(status().isUnauthorized()); } @Test @@ -304,10 +302,6 @@ public class InterceptUrlConfigTests { // @formatter:off this.mvc.perform(get("/spring/path").servletPath("/spring")) .andExpect(status().isUnauthorized()); - this.mvc.perform(get("/spring/path.html").servletPath("/spring")) - .andExpect(status().isUnauthorized()); - this.mvc.perform(get("/spring/path/").servletPath("/spring")) - .andExpect(status().isUnauthorized()); // @formatter:on } 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 dfede958e7..49d40a29c3 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 @@ -150,26 +150,6 @@ class AuthorizeHttpRequestsDslTests { } } - @Test - fun `request when allowed by mvc then responds with OK`() { - this.spring.register(AuthorizeHttpRequestsByMvcConfig::class.java, LegacyMvcMatchingConfig::class.java).autowire() - - this.mockMvc.get("/path") - .andExpect { - status { isOk() } - } - - this.mockMvc.get("/path.html") - .andExpect { - status { isOk() } - } - - this.mockMvc.get("/path/") - .andExpect { - status { isOk() } - } - } - @Configuration @EnableWebSecurity @EnableWebMvc @@ -193,14 +173,6 @@ class AuthorizeHttpRequestsDslTests { } } - @Configuration - open class LegacyMvcMatchingConfig : WebMvcConfigurer { - override fun configurePathMatch(configurer: PathMatchConfigurer) { - configurer.setUseSuffixPatternMatch(true) - configurer.setUseTrailingSlashMatch(true) - } - } - @Test fun `request when secured by mvc path variables then responds based on path variable value`() { this.spring.register(MvcMatcherPathVariablesConfig::class.java).autowire() diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeRequestsDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeRequestsDslTests.kt index be39dbd89d..42eda2bbfc 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeRequestsDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeRequestsDslTests.kt @@ -135,26 +135,6 @@ class AuthorizeRequestsDslTests { } } - @Test - fun `request when allowed by mvc then responds with OK`() { - this.spring.register(AuthorizeRequestsByMvcConfig::class.java, LegacyMvcMatchingConfig::class.java).autowire() - - this.mockMvc.get("/path") - .andExpect { - status { isOk() } - } - - this.mockMvc.get("/path.html") - .andExpect { - status { isOk() } - } - - this.mockMvc.get("/path/") - .andExpect { - status { isOk() } - } - } - @Configuration @EnableWebSecurity @EnableWebMvc @@ -179,14 +159,6 @@ class AuthorizeRequestsDslTests { } } - @Configuration - open class LegacyMvcMatchingConfig : WebMvcConfigurer { - override fun configurePathMatch(configurer: PathMatchConfigurer) { - configurer.setUseSuffixPatternMatch(true) - configurer.setUseTrailingSlashMatch(true) - } - } - @Test fun `request when secured by mvc path variables then responds based on path variable value`() { this.spring.register(MvcMatcherPathVariablesConfig::class.java).autowire() diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchers.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchers.xml index f5eeb472ab..44fc3c7dcd 100644 --- a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchers.xml +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchers.xml @@ -33,7 +33,7 @@ - + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersAuthorizationManager.xml index 9edc7c8efa..f63955adef 100644 --- a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersAuthorizationManager.xml +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersAuthorizationManager.xml @@ -33,7 +33,7 @@ - + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPath.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPath.xml index 74dabfbd67..749fc6526a 100644 --- a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPath.xml +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPath.xml @@ -33,7 +33,7 @@ - + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPathAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPathAuthorizationManager.xml index c8a4cc42e9..9b6e686e7b 100644 --- a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPathAuthorizationManager.xml +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPathAuthorizationManager.xml @@ -33,7 +33,7 @@ - + From 11105a5c510876f170a43a2285b6a14a3753e6a0 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 10:58:48 -0500 Subject: [PATCH 006/149] UriComponentsBuilder.fromHttpUrl->fromUriString The fromHttpUrl method is deprecated and replaced with fromUriString Closes gh- --- .../web/configurers/saml2/Saml2LoginConfigurerTests.java | 4 ++-- .../client/web/OAuth2LoginAuthenticationFilterTests.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java index 6d874a583d..4d262c91d5 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java @@ -253,7 +253,7 @@ public class Saml2LoginConfigurerTests { public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception { this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire(); MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn(); - UriComponents components = UriComponentsBuilder.fromHttpUrl(result.getResponse().getRedirectedUrl()).build(); + UriComponents components = UriComponentsBuilder.fromUriString(result.getResponse().getRedirectedUrl()).build(); String samlRequest = components.getQueryParams().getFirst("SAMLRequest"); String decoded = URLDecoder.decode(samlRequest, "UTF-8"); String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded)); @@ -264,7 +264,7 @@ public class Saml2LoginConfigurerTests { public void authenticationRequestWhenAuthenticationRequestResolverDslThenUses() throws Exception { this.spring.register(CustomAuthenticationRequestResolverDsl.class).autowire(); MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn(); - UriComponents components = UriComponentsBuilder.fromHttpUrl(result.getResponse().getRedirectedUrl()).build(); + UriComponents components = UriComponentsBuilder.fromUriString(result.getResponse().getRedirectedUrl()).build(); String samlRequest = components.getQueryParams().getFirst("SAMLRequest"); String decoded = URLDecoder.decode(samlRequest, "UTF-8"); String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded)); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilterTests.java index bd813ba9e1..3dee3c0cc0 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilterTests.java @@ -475,7 +475,7 @@ public class OAuth2LoginAuthenticationFilterTests { } private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration) { - String baseUrl = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) + String baseUrl = UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request)) .replaceQuery(null) .replacePath(request.getContextPath()) .build() From b467c47ed5c1784a6535b7b97635cbfd2c3b0aad Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 11:06:26 -0500 Subject: [PATCH 007/149] ClientRequest.method->create ClientRequest.method was deprecated in favor of the create method Closes gh-17054 --- .../security/htmlunit/server/HtmlUnitWebTestClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/src/test/java/org/springframework/security/htmlunit/server/HtmlUnitWebTestClient.java b/config/src/test/java/org/springframework/security/htmlunit/server/HtmlUnitWebTestClient.java index bd48fdf85a..29ebaecd42 100644 --- a/config/src/test/java/org/springframework/security/htmlunit/server/HtmlUnitWebTestClient.java +++ b/config/src/test/java/org/springframework/security/htmlunit/server/HtmlUnitWebTestClient.java @@ -161,7 +161,7 @@ final class HtmlUnitWebTestClient { redirectUrl = scheme + "://" + host + location.toASCIIString(); } // @formatter:off - ClientRequest redirect = ClientRequest.method(HttpMethod.GET, URI.create(redirectUrl)) + ClientRequest redirect = ClientRequest.create(HttpMethod.GET, URI.create(redirectUrl)) .headers((headers) -> headers.addAll(request.headers())) .cookies((cookies) -> cookies.addAll(request.cookies())) .attributes((attributes) -> attributes.putAll(request.attributes())) From 3976e7d456438a352d1d7e8db13f369f3eefecff Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 11:08:26 -0500 Subject: [PATCH 008/149] BodyInserters.fromObject -> fromProducer Closes gh-17055 --- .../security/htmlunit/server/HtmlUnitWebTestClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/src/test/java/org/springframework/security/htmlunit/server/HtmlUnitWebTestClient.java b/config/src/test/java/org/springframework/security/htmlunit/server/HtmlUnitWebTestClient.java index 29ebaecd42..6389ea83ae 100644 --- a/config/src/test/java/org/springframework/security/htmlunit/server/HtmlUnitWebTestClient.java +++ b/config/src/test/java/org/springframework/security/htmlunit/server/HtmlUnitWebTestClient.java @@ -85,7 +85,7 @@ final class HtmlUnitWebTestClient { } return request; } - return request.body(BodyInserters.fromObject(requestBody)); + return request.body(BodyInserters.fromProducer(requestBody, String.class)); } private MultiValueMap formData(List params) { From 5704582c521ba8aa9861740eedde45db762eaa26 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 11:41:01 -0500 Subject: [PATCH 009/149] ResponseErrorHandler.handleError(URI, HttpMethod,ClientHttpResponse) Closes gh-17056 --- .../http/OAuth2ErrorResponseErrorHandler.java | 6 ++++-- .../OAuth2ErrorResponseErrorHandlerTests.java | 16 +++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandler.java index 0b7c058757..cb692846c5 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandler.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandler.java @@ -17,10 +17,12 @@ package org.springframework.security.oauth2.client.http; import java.io.IOException; +import java.net.URI; import com.nimbusds.oauth2.sdk.token.BearerTokenError; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.converter.HttpMessageConverter; @@ -53,9 +55,9 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler { } @Override - public void handleError(ClientHttpResponse response) throws IOException { + public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { if (HttpStatus.BAD_REQUEST.value() != response.getStatusCode().value()) { - this.defaultErrorHandler.handleError(response); + this.defaultErrorHandler.handleError(url, method, response); } // A Bearer Token Error may be in the WWW-Authenticate response header // See https://tools.ietf.org/html/rfc6750#section-3 diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandlerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandlerTests.java index 602273c52e..b2636c4024 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandlerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandlerTests.java @@ -17,10 +17,12 @@ package org.springframework.security.oauth2.client.http; import java.io.IOException; +import java.net.URI; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.converter.HttpMessageConverter; @@ -45,6 +47,10 @@ public class OAuth2ErrorResponseErrorHandlerTests { private OAuth2ErrorResponseErrorHandler errorHandler = new OAuth2ErrorResponseErrorHandler(); + private URI anyURi = URI.create("/any"); + + private HttpMethod anyMethod = HttpMethod.GET; + @Test public void handleErrorWhenErrorResponseBodyThenHandled() { // @formatter:off @@ -55,7 +61,7 @@ public class OAuth2ErrorResponseErrorHandlerTests { // @formatter:on MockClientHttpResponse response = new MockClientHttpResponse(errorResponse.getBytes(), HttpStatus.BAD_REQUEST); assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.errorHandler.handleError(response)) + .isThrownBy(() -> this.errorHandler.handleError(this.anyURi, this.anyMethod, response)) .withMessage("[unauthorized_client] The client is not authorized"); } @@ -74,7 +80,7 @@ public class OAuth2ErrorResponseErrorHandlerTests { .willReturn(new OAuth2Error("unauthorized_client", "The client is not authorized", null)); assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.errorHandler.handleError(response)) + .isThrownBy(() -> this.errorHandler.handleError(this.anyURi, this.anyMethod, response)) .withMessage("[unauthorized_client] The client is not authorized"); verify(oauth2ErrorConverter).read(eq(OAuth2Error.class), eq(response)); } @@ -85,7 +91,7 @@ public class OAuth2ErrorResponseErrorHandlerTests { MockClientHttpResponse response = new MockClientHttpResponse(new byte[0], HttpStatus.BAD_REQUEST); response.getHeaders().add(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticateHeader); assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.errorHandler.handleError(response)) + .isThrownBy(() -> this.errorHandler.handleError(this.anyURi, this.anyMethod, response)) .withMessage("[insufficient_scope] The access token expired"); } @@ -95,7 +101,7 @@ public class OAuth2ErrorResponseErrorHandlerTests { MockClientHttpResponse response = new MockClientHttpResponse(new byte[0], HttpStatus.BAD_REQUEST); response.getHeaders().add(HttpHeaders.WWW_AUTHENTICATE, invalidWwwAuthenticateHeader); assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.errorHandler.handleError(response)) + .isThrownBy(() -> this.errorHandler.handleError(this.anyURi, this.anyMethod, response)) .withMessage("[server_error] "); } @@ -103,7 +109,7 @@ public class OAuth2ErrorResponseErrorHandlerTests { public void handleErrorWhenErrorResponseWithInvalidStatusCodeThenHandled() { CustomMockClientHttpResponse response = new CustomMockClientHttpResponse(new byte[0], 596); assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> this.errorHandler.handleError(response)) + .isThrownBy(() -> this.errorHandler.handleError(this.anyURi, this.anyMethod, response)) .withMessage("No matching constant for [596]"); } From 2dbf3a2d181febebccf3f6268da19e85dd28244e Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 11:41:34 -0500 Subject: [PATCH 010/149] WebClient.exchange->exchangeToMono Closes gh-17057 --- .../NimbusReactiveOpaqueTokenIntrospector.java | 10 ++++------ .../NimbusReactiveOpaqueTokenIntrospectorTests.java | 13 +++++++++++-- .../SpringReactiveOpaqueTokenIntrospectorTests.java | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java index b7debd105c..6593248360 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java @@ -98,9 +98,8 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke @Override public Mono introspect(String token) { // @formatter:off - return Mono.just(token) - .flatMap(this::makeRequest) - .flatMap(this::adaptToNimbusResponse) + return this.makeRequest(token) + .exchangeToMono(this::adaptToNimbusResponse) .map(this::parseNimbusResponse) .map(this::castToNimbusSuccess) .doOnNext((response) -> validate(token, response)) @@ -109,13 +108,12 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke // @formatter:on } - private Mono makeRequest(String token) { + private WebClient.RequestHeadersSpec makeRequest(String token) { // @formatter:off return this.webClient.post() .uri(this.introspectionUri) .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .body(BodyInserters.fromFormData("token", token)) - .exchange(); + .body(BodyInserters.fromFormData("token", token)); // @formatter:on } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java index 68ff78a2ac..0b8dd246cd 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java @@ -23,6 +23,7 @@ import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.function.Function; import net.minidev.json.JSONObject; import okhttp3.mockwebserver.Dispatcher; @@ -45,6 +46,7 @@ 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.spy; @@ -265,6 +267,7 @@ public class NimbusReactiveOpaqueTokenIntrospectorTests { } private WebClient mockResponse(String response, String mediaType) { + WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class); WebClient real = WebClient.builder().build(); WebClient.RequestBodyUriSpec spec = spy(real.post()); WebClient webClient = spy(WebClient.class); @@ -275,7 +278,13 @@ public class NimbusReactiveOpaqueTokenIntrospectorTests { ClientResponse.Headers headers = mock(ClientResponse.Headers.class); given(headers.contentType()).willReturn(Optional.ofNullable(mediaType).map(MediaType::parseMediaType)); given(clientResponse.headers()).willReturn(headers); - given(spec.exchange()).willReturn(Mono.just(clientResponse)); + given(responseSpec.bodyToMono(ClientResponse.class)).willReturn(Mono.just(clientResponse)); + given(spec.exchangeToMono(any())).willAnswer((invocation) -> { + Object[] args = invocation.getArguments(); + Function> fn = (Function>) args[0]; + return fn.apply(clientResponse); + }); + given(spec.retrieve()).willReturn(responseSpec); return webClient; } @@ -284,7 +293,7 @@ public class NimbusReactiveOpaqueTokenIntrospectorTests { WebClient.RequestBodyUriSpec spec = spy(real.post()); WebClient webClient = spy(WebClient.class); given(webClient.post()).willReturn(spec); - given(spec.exchange()).willThrow(ex); + given(spec.exchangeToMono(any())).willReturn(Mono.error(ex)); return webClient; } 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 da9d22df23..8f05bd1220 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 @@ -345,7 +345,7 @@ public class SpringReactiveOpaqueTokenIntrospectorTests { WebClient.RequestBodyUriSpec spec = spy(real.post()); WebClient webClient = spy(WebClient.class); given(webClient.post()).willReturn(spec); - given(spec.exchange()).willThrow(ex); + given(spec.exchangeToMono(any())).willThrow(ex); return webClient; } From 66319fc3bc3ef0284f51366545fd4670908c0d3d Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 11:43:06 -0500 Subject: [PATCH 011/149] MockServerHttpRequest.method(String,String)->method(HttpMethod,String) Closes gh-17058 --- .../security/web/server/csrf/CsrfWebFilterTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java b/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java index a60c604914..81c6e6deb8 100644 --- a/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java @@ -24,6 +24,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import reactor.test.publisher.PublisherProbe; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; @@ -216,7 +217,7 @@ public class CsrfWebFilterTests { // gh-8452 public void matchesRequireCsrfProtectionWhenNonStandardHTTPMethodIsUsed() { MockServerWebExchange nonStandardHttpExchange = MockServerWebExchange - .from(MockServerHttpRequest.method("non-standard-http-method", "/")); + .from(MockServerHttpRequest.method(HttpMethod.valueOf("non-standard-http-method"), "/")); ServerWebExchangeMatcher serverWebExchangeMatcher = CsrfWebFilter.DEFAULT_CSRF_MATCHER; assertThat(serverWebExchangeMatcher.matches(nonStandardHttpExchange).map(MatchResult::isMatch).block()) .isTrue(); From 607705347c24df3412ce44b1c66acb8d7b5518ce Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 11:43:22 -0500 Subject: [PATCH 012/149] MediaType.sortBySpecificityAndQuality->sortBySpecificity Closes gh-17059 --- .../server/util/matcher/MediaTypeServerWebExchangeMatcher.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/server/util/matcher/MediaTypeServerWebExchangeMatcher.java b/web/src/main/java/org/springframework/security/web/server/util/matcher/MediaTypeServerWebExchangeMatcher.java index eb6e5a315d..22c661e96b 100644 --- a/web/src/main/java/org/springframework/security/web/server/util/matcher/MediaTypeServerWebExchangeMatcher.java +++ b/web/src/main/java/org/springframework/security/web/server/util/matcher/MediaTypeServerWebExchangeMatcher.java @@ -30,6 +30,7 @@ import org.springframework.core.log.LogMessage; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.util.Assert; +import org.springframework.util.MimeTypeUtils; import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; @@ -138,7 +139,7 @@ public class MediaTypeServerWebExchangeMatcher implements ServerWebExchangeMatch private List resolveMediaTypes(ServerWebExchange exchange) throws NotAcceptableStatusException { try { List mediaTypes = exchange.getRequest().getHeaders().getAccept(); - MediaType.sortBySpecificityAndQuality(mediaTypes); + MimeTypeUtils.sortBySpecificity(mediaTypes); return mediaTypes; } catch (InvalidMediaTypeException ex) { From 0e2d864b09b23e74342df06e9e695202147d2e30 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 11:44:14 -0500 Subject: [PATCH 013/149] Update Versions --- gradle.properties | 2 +- gradle/libs.versions.toml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gradle.properties b/gradle.properties index dc0e335d87..19a07278b1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -springBootVersion=3.3.3 +springBootVersion=4.0.0-SNAPSHOT version=7.0.0-SNAPSHOT samplesBranch=main org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 56d8410fd6..9a4ed86714 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ org-jetbrains-kotlinx = "1.10.2" org-mockito = "5.17.0" org-opensaml = "4.3.2" org-opensaml5 = "5.1.2" -org-springframework = "6.2.6" +org-springframework = "7.0.0-SNAPSHOT" [libraries] ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.18" @@ -31,18 +31,18 @@ commons-collections = "commons-collections:commons-collections:3.2.2" io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3" io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.6" io-mockk = "io.mockk:mockk:1.14.2" -io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.17" +io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-M2" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" } io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" } io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" } io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" } io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.6" -jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:2.1.1" +jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:3.0.0" jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1" -jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.1.0" -jakarta-servlet-jakarta-servlet-api = "jakarta.servlet:jakarta.servlet-api:6.0.0" -jakarta-servlet-jsp-jakarta-servlet-jsp-api = "jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.1" +jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.2.0" +jakarta-servlet-jakarta-servlet-api = "jakarta.servlet:jakarta.servlet-api:6.1.0" +jakarta-servlet-jsp-jakarta-servlet-jsp-api = "jakarta.servlet.jsp:jakarta.servlet.jsp-api:4.0.0" jakarta-servlet-jsp-jstl-jakarta-servlet-jsp-jstl-api = "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.2" jakarta-websocket-jakarta-websocket-api = { module = "jakarta.websocket:jakarta.websocket-api", version.ref = "jakarta-websocket" } jakarta-websocket-jakarta-websocket-client-api = { module = "jakarta.websocket:jakarta.websocket-client-api", version.ref = "jakarta-websocket" } @@ -71,7 +71,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", 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.6.13.Final" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.0.0.CR1" org-hsqldb = "org.hsqldb:hsqldb:2.7.4" 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.25" @@ -89,7 +89,7 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17" -org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.5" +org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.0-SNAPSHOT" org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.12" 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" From e5e962ef90f062100cffa07a3c91c52ddba7c667 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 11:41:59 -0500 Subject: [PATCH 014/149] Jakarta Cookie HttpOnly Serialization The new specification represents Cookie attribute using HttpOnly: "" vs HttpOnly: "true". This updates the test to correspond to the new Servlet specification and is a breaking change related to jakarta updates. --- .../security/web/jackson2/CookieDeserializer.java | 2 +- .../springframework/security/web/jackson2/CookieMixinTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/jackson2/CookieDeserializer.java b/web/src/main/java/org/springframework/security/web/jackson2/CookieDeserializer.java index 29a4ca231b..d4123a8188 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/CookieDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/CookieDeserializer.java @@ -52,7 +52,7 @@ class CookieDeserializer extends JsonDeserializer { cookie.setVersion(readJsonNode(jsonNode, "version").asInt()); cookie.setPath(readJsonNode(jsonNode, "path").asText()); JsonNode attributes = readJsonNode(jsonNode, "attributes"); - cookie.setHttpOnly(readJsonNode(attributes, "HttpOnly").asBoolean()); + cookie.setHttpOnly(readJsonNode(attributes, "HttpOnly") != null); return cookie; } diff --git a/web/src/test/java/org/springframework/security/web/jackson2/CookieMixinTests.java b/web/src/test/java/org/springframework/security/web/jackson2/CookieMixinTests.java index 0e8cdfd032..3b14b222ee 100644 --- a/web/src/test/java/org/springframework/security/web/jackson2/CookieMixinTests.java +++ b/web/src/test/java/org/springframework/security/web/jackson2/CookieMixinTests.java @@ -52,7 +52,7 @@ public class CookieMixinTests extends AbstractMixinTests { " \"@class\": \"jakarta.servlet.http.Cookie\"," + " \"name\": \"demo\"," + " \"value\": \"cookie1\"," + - " \"attributes\":{\"@class\":\"java.util.Collections$UnmodifiableMap\", \"HttpOnly\": \"true\"}," + + " \"attributes\":{\"@class\":\"java.util.Collections$UnmodifiableMap\", \"HttpOnly\": \"\"}," + " \"comment\": null," + " \"maxAge\": -1," + " \"path\": null," + From b453840c0a0296bacb5d8182a64bd6a2c16adda6 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 11:10:28 -0500 Subject: [PATCH 015/149] HttpHeaders no longer a MultiValueMap Closes gh-17060 --- .../config/web/server/CorsSpecTests.java | 9 ++++--- .../config/web/server/HeaderSpecTests.java | 13 +++++----- .../web/server/OidcLogoutSpecTests.java | 2 +- .../web/server/ServerHttpsRedirectDslTests.kt | 4 ++-- ...enServerAuthenticationEntryPointTests.java | 2 +- .../security/web/FilterInvocation.java | 2 +- .../StrictServerWebExchangeFirewall.java | 24 ++++++++----------- .../header/StaticServerHttpHeadersWriter.java | 4 ++-- .../StrictServerWebExchangeFirewallTests.java | 2 +- ...heControlServerHttpHeadersWriterTests.java | 8 +++---- ...ityPolicyServerHttpHeadersWriterTests.java | 10 ++++---- ...peOptionsServerHttpHeadersWriterTests.java | 4 ++-- ...derPolicyServerHttpHeadersWriterTests.java | 6 ++--- ...nerPolicyServerHttpHeadersWriterTests.java | 6 ++--- ...rcePolicyServerHttpHeadersWriterTests.java | 6 ++--- ...urePolicyServerHttpHeadersWriterTests.java | 6 ++--- ...onsPolicyServerHttpHeadersWriterTests.java | 6 ++--- ...rerPolicyServerHttpHeadersWriterTests.java | 6 ++--- .../StaticServerHttpHeadersWriterTests.java | 2 +- ...tSecurityServerHttpHeadersWriterTests.java | 24 +++++++++---------- ...peOptionsServerHttpHeadersWriterTests.java | 4 ++-- ...meOptionsServerHttpHeadersWriterTests.java | 8 +++---- ...rotectionServerHttpHeadersWriterTests.java | 10 ++++---- 23 files changed, 81 insertions(+), 87 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/web/server/CorsSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/CorsSpecTests.java index 63646400ef..3ae317d751 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/CorsSpecTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/CorsSpecTests.java @@ -18,8 +18,6 @@ package org.springframework.security.config.web.server; import java.util.Arrays; import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Set; import org.junit.jupiter.api.BeforeEach; @@ -114,12 +112,13 @@ public class CorsSpecTests { .exchange() .returnResult(String.class); // @formatter:on - Map> responseHeaders = response.getResponseHeaders(); + HttpHeaders responseHeaders = response.getResponseHeaders(); if (!this.expectedHeaders.isEmpty()) { - assertThat(responseHeaders).describedAs(response.toString()).containsAllEntriesOf(this.expectedHeaders); + this.expectedHeaders.forEach( + (headerName, headerValues) -> assertThat(responseHeaders.get(headerName)).isEqualTo(headerValues)); } if (!this.headerNamesNotPresent.isEmpty()) { - assertThat(responseHeaders.keySet()).doesNotContainAnyElementsOf(this.headerNamesNotPresent); + assertThat(responseHeaders.headerNames()).doesNotContainAnyElementsOf(this.headerNamesNotPresent); } } diff --git a/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java index 50dbe184b2..486cf40798 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java @@ -18,8 +18,6 @@ package org.springframework.security.config.web.server; import java.time.Duration; import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Set; import org.junit.jupiter.api.BeforeEach; @@ -80,14 +78,14 @@ public class HeaderSpecTests { @Test public void headersWhenDisableThenNoSecurityHeaders() { - new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent); + new HashSet<>(this.expectedHeaders.headerNames()).forEach(this::expectHeaderNamesNotPresent); this.http.headers().disable(); assertHeaders(); } @Test public void headersWhenDisableInLambdaThenNoSecurityHeaders() { - new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent); + new HashSet<>(this.expectedHeaders.headerNames()).forEach(this::expectHeaderNamesNotPresent); this.http.headers((headers) -> headers.disable()); assertHeaders(); } @@ -515,12 +513,13 @@ public class HeaderSpecTests { .uri("https://example.com/") .exchange() .returnResult(String.class); - Map> responseHeaders = response.getResponseHeaders(); + HttpHeaders responseHeaders = response.getResponseHeaders(); if (!this.expectedHeaders.isEmpty()) { - assertThat(responseHeaders).describedAs(response.toString()).containsAllEntriesOf(this.expectedHeaders); + this.expectedHeaders.forEach( + (headerName, headerValues) -> assertThat(responseHeaders.get(headerName)).isEqualTo(headerValues)); } if (!this.headerNamesNotPresent.isEmpty()) { - assertThat(responseHeaders.keySet()).doesNotContainAnyElementsOf(this.headerNamesNotPresent); + assertThat(responseHeaders.headerNames()).doesNotContainAnyElementsOf(this.headerNamesNotPresent); } } diff --git a/config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java index ebff5d102b..5c5a9cb44b 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java @@ -945,7 +945,7 @@ public class OidcLogoutSpecTests { private MockResponse toMockResponse(FluxExchangeResult result) { MockResponse response = new MockResponse(); response.setResponseCode(result.getStatus().value()); - for (String name : result.getResponseHeaders().keySet()) { + for (String name : result.getResponseHeaders().headerNames()) { response.addHeader(name, result.getResponseHeaders().getFirst(name)); } String body = result.getResponseBody().blockFirst(); diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpsRedirectDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpsRedirectDslTests.kt index 3d302582dd..8588447cb0 100644 --- a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpsRedirectDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpsRedirectDslTests.kt @@ -127,7 +127,7 @@ class ServerHttpsRedirectDslTests { return http { redirectToHttps { httpsRedirectWhen { - it.request.headers.containsKey("X-Requires-Https") + it.request.headers.headerNames().contains("X-Requires-Https") } } } @@ -165,7 +165,7 @@ class ServerHttpsRedirectDslTests { redirectToHttps { httpsRedirectWhen(PathPatternParserServerWebExchangeMatcher("/secure")) httpsRedirectWhen { - it.request.headers.containsKey("X-Requires-Https") + it.request.headers.headerNames().contains("X-Requires-Https") } } } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/BearerTokenServerAuthenticationEntryPointTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/BearerTokenServerAuthenticationEntryPointTests.java index c8df65119c..8f25f4e710 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/BearerTokenServerAuthenticationEntryPointTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/BearerTokenServerAuthenticationEntryPointTests.java @@ -91,7 +91,7 @@ public class BearerTokenServerAuthenticationEntryPointTests { @Test public void commenceWhenNoSubscriberThenNothingHappens() { this.entryPoint.commence(this.exchange, new BadCredentialsException("")); - assertThat(getResponse().getHeaders()).isEmpty(); + assertThat(getResponse().getHeaders().headerNames()).isEmpty(); assertThat(getResponse().getStatusCode()).isNull(); } diff --git a/web/src/main/java/org/springframework/security/web/FilterInvocation.java b/web/src/main/java/org/springframework/security/web/FilterInvocation.java index f9f86476c8..c5fdf8e2d8 100644 --- a/web/src/main/java/org/springframework/security/web/FilterInvocation.java +++ b/web/src/main/java/org/springframework/security/web/FilterInvocation.java @@ -267,7 +267,7 @@ public class FilterInvocation { @Override public Enumeration getHeaderNames() { - return Collections.enumeration(this.headers.keySet()); + return Collections.enumeration(this.headers.headerNames()); } @Override diff --git a/web/src/main/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewall.java b/web/src/main/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewall.java index 72871f2b45..8c6054cd73 100644 --- a/web/src/main/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewall.java +++ b/web/src/main/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewall.java @@ -135,8 +135,8 @@ public class StrictServerWebExchangeFirewall implements ServerWebExchangeFirewal private static final Pattern ASSIGNED_AND_NOT_ISO_CONTROL_PATTERN = Pattern .compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*"); - private static final Predicate ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE = ( - s) -> ASSIGNED_AND_NOT_ISO_CONTROL_PATTERN.matcher(s).matches(); + private static final Predicate ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE = (s) -> s == null + || ASSIGNED_AND_NOT_ISO_CONTROL_PATTERN.matcher(s).matches(); private static final Pattern HEADER_VALUE_PATTERN = Pattern.compile("[\\p{IsAssigned}&&[[^\\p{IsControl}]||\\t]]*"); @@ -198,13 +198,11 @@ public class StrictServerWebExchangeFirewall implements ServerWebExchangeFirewal exchange.getResponse().beforeCommit(() -> Mono.fromRunnable(() -> { ServerHttpResponse response = exchange.getResponse(); HttpHeaders headers = response.getHeaders(); - for (Map.Entry> header : headers.entrySet()) { - String headerName = header.getKey(); - List headerValues = header.getValue(); + headers.forEach((headerName, headerValues) -> { for (String headerValue : headerValues) { validateCrlf(headerName, headerValue); } - } + }); })); return new StrictFirewallServerWebExchange(exchange); }); @@ -767,23 +765,21 @@ public class StrictServerWebExchangeFirewall implements ServerWebExchangeFirewal } @Override - public List get(Object key) { - if (key instanceof String headerName) { - validateAllowedHeaderName(headerName); - } - List headerValues = super.get(key); + public List get(String headerName) { + validateAllowedHeaderName(headerName); + List headerValues = super.get(headerName); if (headerValues == null) { return headerValues; } for (String headerValue : headerValues) { - validateAllowedHeaderValue(key, headerValue); + validateAllowedHeaderValue(headerName, headerValue); } return headerValues; } @Override - public Set keySet() { - Set headerNames = super.keySet(); + public Set headerNames() { + Set headerNames = super.headerNames(); for (String headerName : headerNames) { validateAllowedHeaderName(headerName); } diff --git a/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java index fb3d3c4d77..6ea83366a8 100644 --- a/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java @@ -43,8 +43,8 @@ public class StaticServerHttpHeadersWriter implements ServerHttpHeadersWriter { // Note: We need to ensure that the following algorithm compares headers // case insensitively, which should be true of headers.containsKey(). boolean containsNoHeadersToAdd = true; - for (String headerName : this.headersToAdd.keySet()) { - if (headers.containsKey(headerName)) { + for (String headerName : this.headersToAdd.headerNames()) { + if (headers.containsHeader(headerName)) { containsNoHeadersToAdd = false; break; } diff --git a/web/src/test/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewallTests.java b/web/src/test/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewallTests.java index 8f24b0522c..6d81eafe24 100644 --- a/web/src/test/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewallTests.java +++ b/web/src/test/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewallTests.java @@ -444,7 +444,7 @@ class StrictServerWebExchangeFirewallTests { ServerWebExchange exchange = getFirewalledExchange(); HttpHeaders headers = exchange.getRequest().getHeaders(); assertThatExceptionOfType(ServerExchangeRejectedException.class) - .isThrownBy(() -> headers.keySet().iterator().next()); + .isThrownBy(() -> headers.headerNames().iterator().next()); } @Test diff --git a/web/src/test/java/org/springframework/security/web/server/header/CacheControlServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/CacheControlServerHttpHeadersWriterTests.java index 822750041a..e9e14518db 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/CacheControlServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/CacheControlServerHttpHeadersWriterTests.java @@ -42,7 +42,7 @@ public class CacheControlServerHttpHeadersWriterTests { @Test public void writeHeadersWhenCacheHeadersThenWritesAllCacheControl() { this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(3); + assertThat(this.headers.headerNames()).hasSize(3); assertThat(this.headers.get(HttpHeaders.CACHE_CONTROL)) .containsOnly(CacheControlServerHttpHeadersWriter.CACHE_CONTRTOL_VALUE); assertThat(this.headers.get(HttpHeaders.EXPIRES)) @@ -63,7 +63,7 @@ public class CacheControlServerHttpHeadersWriterTests { String pragma = "1"; this.headers.set(HttpHeaders.PRAGMA, pragma); this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(HttpHeaders.PRAGMA)).containsOnly(pragma); } @@ -72,7 +72,7 @@ public class CacheControlServerHttpHeadersWriterTests { String expires = "1"; this.headers.set(HttpHeaders.EXPIRES, expires); this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(HttpHeaders.EXPIRES)).containsOnly(expires); } @@ -81,7 +81,7 @@ public class CacheControlServerHttpHeadersWriterTests { public void writeHeadersWhenNotModifiedThenNoCacheControlHeaders() { this.exchange.getResponse().setStatusCode(HttpStatus.NOT_MODIFIED); this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).isEmpty(); + assertThat(this.headers.headerNames()).isEmpty(); } } diff --git a/web/src/test/java/org/springframework/security/web/server/header/ContentSecurityPolicyServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/ContentSecurityPolicyServerHttpHeadersWriterTests.java index 28efe3b190..984f309f41 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/ContentSecurityPolicyServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/ContentSecurityPolicyServerHttpHeadersWriterTests.java @@ -49,7 +49,7 @@ public class ContentSecurityPolicyServerHttpHeadersWriterTests { public void writeHeadersWhenUsingDefaultsThenDoesNotWrite() { this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).isEmpty(); + assertThat(headers.headerNames()).isEmpty(); } @Test @@ -57,7 +57,7 @@ public class ContentSecurityPolicyServerHttpHeadersWriterTests { this.writer.setPolicyDirectives(DEFAULT_POLICY_DIRECTIVES); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY)) .containsOnly(DEFAULT_POLICY_DIRECTIVES); } @@ -68,7 +68,7 @@ public class ContentSecurityPolicyServerHttpHeadersWriterTests { this.writer.setReportOnly(true); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY_REPORT_ONLY)) .containsOnly(DEFAULT_POLICY_DIRECTIVES); } @@ -78,7 +78,7 @@ public class ContentSecurityPolicyServerHttpHeadersWriterTests { this.writer.setReportOnly(true); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).isEmpty(); + assertThat(headers.headerNames()).isEmpty(); } @Test @@ -89,7 +89,7 @@ public class ContentSecurityPolicyServerHttpHeadersWriterTests { .set(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, headerValue); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY)) .containsOnly(headerValue); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/ContentTypeOptionsServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/ContentTypeOptionsServerHttpHeadersWriterTests.java index 4221e36a1e..1258ded69c 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/ContentTypeOptionsServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/ContentTypeOptionsServerHttpHeadersWriterTests.java @@ -41,7 +41,7 @@ class ContentTypeOptionsServerHttpHeadersWriterTests { @Test void writeHeadersWhenNoHeadersThenWriteHeaders() { this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)) .containsOnly(ContentTypeOptionsServerHttpHeadersWriter.NOSNIFF); } @@ -51,7 +51,7 @@ class ContentTypeOptionsServerHttpHeadersWriterTests { String headerValue = "value"; this.headers.set(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, headerValue); this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)) .containsOnly(headerValue); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java index c4be94b856..23c3f8695d 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java @@ -49,7 +49,7 @@ class CrossOriginEmbedderPolicyServerHttpHeadersWriterTests { void writeHeadersWhenNoValuesThenDoesNotWriteHeaders() { this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).isEmpty(); + assertThat(headers.headerNames()).isEmpty(); } @Test @@ -59,7 +59,7 @@ class CrossOriginEmbedderPolicyServerHttpHeadersWriterTests { .add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY, "require-corp"); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY)) .containsOnly("require-corp"); } @@ -69,7 +69,7 @@ class CrossOriginEmbedderPolicyServerHttpHeadersWriterTests { this.writer.setPolicy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY)) .containsOnly("require-corp"); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriterTests.java index 0f5f993377..8e44be6633 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriterTests.java @@ -49,7 +49,7 @@ class CrossOriginOpenerPolicyServerHttpHeadersWriterTests { void writeHeadersWhenNoValuesThenDoesNotWriteHeaders() { this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).isEmpty(); + assertThat(headers.headerNames()).isEmpty(); } @Test @@ -59,7 +59,7 @@ class CrossOriginOpenerPolicyServerHttpHeadersWriterTests { .add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY, "same-origin"); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY)) .containsOnly("same-origin"); } @@ -70,7 +70,7 @@ class CrossOriginOpenerPolicyServerHttpHeadersWriterTests { .setPolicy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY)) .containsOnly("same-origin-allow-popups"); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriterTests.java index 01b1242f27..bab94892c9 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriterTests.java @@ -49,7 +49,7 @@ class CrossOriginResourcePolicyServerHttpHeadersWriterTests { void writeHeadersWhenNoValuesThenDoesNotWriteHeaders() { this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).isEmpty(); + assertThat(headers.headerNames()).isEmpty(); } @Test @@ -59,7 +59,7 @@ class CrossOriginResourcePolicyServerHttpHeadersWriterTests { .add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY, "same-origin"); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY)) .containsOnly("same-origin"); } @@ -69,7 +69,7 @@ class CrossOriginResourcePolicyServerHttpHeadersWriterTests { this.writer.setPolicy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY)) .containsOnly("same-origin"); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/FeaturePolicyServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/FeaturePolicyServerHttpHeadersWriterTests.java index 8e03858642..bc0d952fb8 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/FeaturePolicyServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/FeaturePolicyServerHttpHeadersWriterTests.java @@ -49,7 +49,7 @@ public class FeaturePolicyServerHttpHeadersWriterTests { public void writeHeadersWhenUsingDefaultsThenDoesNotWrite() { this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).isEmpty(); + assertThat(headers.headerNames()).isEmpty(); } @Test @@ -57,7 +57,7 @@ public class FeaturePolicyServerHttpHeadersWriterTests { this.writer.setPolicyDirectives(DEFAULT_POLICY_DIRECTIVES); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(FeaturePolicyServerHttpHeadersWriter.FEATURE_POLICY)) .containsOnly(DEFAULT_POLICY_DIRECTIVES); } @@ -69,7 +69,7 @@ public class FeaturePolicyServerHttpHeadersWriterTests { this.exchange.getResponse().getHeaders().set(FeaturePolicyServerHttpHeadersWriter.FEATURE_POLICY, headerValue); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(FeaturePolicyServerHttpHeadersWriter.FEATURE_POLICY)).containsOnly(headerValue); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriterTests.java index 8c41443839..1a222bdb73 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriterTests.java @@ -49,7 +49,7 @@ public class PermissionsPolicyServerHttpHeadersWriterTests { public void writeHeadersWhenUsingDefaultsThenDoesNotWrite() { this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).isEmpty(); + assertThat(headers.headerNames()).isEmpty(); } @Test @@ -57,7 +57,7 @@ public class PermissionsPolicyServerHttpHeadersWriterTests { this.writer.setPolicy(DEFAULT_POLICY_DIRECTIVES); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(PermissionsPolicyServerHttpHeadersWriter.PERMISSIONS_POLICY)) .containsOnly(DEFAULT_POLICY_DIRECTIVES); } @@ -71,7 +71,7 @@ public class PermissionsPolicyServerHttpHeadersWriterTests { .set(PermissionsPolicyServerHttpHeadersWriter.PERMISSIONS_POLICY, headerValue); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(PermissionsPolicyServerHttpHeadersWriter.PERMISSIONS_POLICY)).containsOnly(headerValue); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/ReferrerPolicyServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/ReferrerPolicyServerHttpHeadersWriterTests.java index 06ca50074a..51b235a6a0 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/ReferrerPolicyServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/ReferrerPolicyServerHttpHeadersWriterTests.java @@ -48,7 +48,7 @@ public class ReferrerPolicyServerHttpHeadersWriterTests { public void writeHeadersWhenUsingDefaultsThenDoesNotWrite() { this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY)) .containsOnly(ReferrerPolicy.NO_REFERRER.getPolicy()); } @@ -58,7 +58,7 @@ public class ReferrerPolicyServerHttpHeadersWriterTests { this.writer.setPolicy(ReferrerPolicy.SAME_ORIGIN); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY)) .containsOnly(ReferrerPolicy.SAME_ORIGIN.getPolicy()); } @@ -71,7 +71,7 @@ public class ReferrerPolicyServerHttpHeadersWriterTests { .set(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY, headerValue); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY)).containsOnly(headerValue); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java index ee3734181a..d11c1af9d4 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java @@ -102,7 +102,7 @@ public class StaticServerHttpHeadersWriterTests { .header(HttpHeaders.EXPIRES, CacheControlServerHttpHeadersWriter.EXPIRES_VALUE) .build(); this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(HttpHeaders.CACHE_CONTROL)).containsOnly(headerValue); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/StrictTransportSecurityServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/StrictTransportSecurityServerHttpHeadersWriterTests.java index 02ed96d3e7..989106e768 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/StrictTransportSecurityServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/StrictTransportSecurityServerHttpHeadersWriterTests.java @@ -17,7 +17,6 @@ package org.springframework.security.web.server.header; import java.time.Duration; -import java.util.Arrays; import org.junit.jupiter.api.Test; @@ -43,9 +42,10 @@ public class StrictTransportSecurityServerHttpHeadersWriterTests { this.exchange = exchange(MockServerHttpRequest.get("https://example.com/")); this.hsts.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); - assertThat(headers).containsEntry(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, - Arrays.asList("max-age=31536000 ; includeSubDomains")); + assertThat(headers.headerNames()).hasSize(1); + assertThat(headers.containsHeaderValue(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, + "max-age=31536000 ; includeSubDomains")) + .isTrue(); } @Test @@ -55,9 +55,9 @@ public class StrictTransportSecurityServerHttpHeadersWriterTests { this.exchange = exchange(MockServerHttpRequest.get("https://example.com/")); this.hsts.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); - assertThat(headers).containsEntry(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, - Arrays.asList("max-age=" + maxAge.getSeconds() + " ; includeSubDomains")); + assertThat(headers.headerNames()).hasSize(1); + assertThat(headers.containsHeaderValue(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, + "max-age=" + maxAge.getSeconds() + " ; includeSubDomains")); } @Test @@ -66,9 +66,9 @@ public class StrictTransportSecurityServerHttpHeadersWriterTests { this.exchange = exchange(MockServerHttpRequest.get("https://example.com/")); this.hsts.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); - assertThat(headers).containsEntry(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, - Arrays.asList("max-age=31536000")); + assertThat(headers.headerNames()).hasSize(1); + assertThat(headers.containsHeaderValue(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, + "max-age=31536000")); } @Test @@ -76,7 +76,7 @@ public class StrictTransportSecurityServerHttpHeadersWriterTests { this.exchange = exchange(MockServerHttpRequest.get("/")); this.hsts.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).isEmpty(); + assertThat(headers.headerNames()).isEmpty(); } @Test @@ -84,7 +84,7 @@ public class StrictTransportSecurityServerHttpHeadersWriterTests { this.exchange = exchange(MockServerHttpRequest.get("http://localhost/")); this.hsts.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).isEmpty(); + assertThat(headers.headerNames()).isEmpty(); } private static MockServerWebExchange exchange(MockServerHttpRequest.BaseBuilder request) { diff --git a/web/src/test/java/org/springframework/security/web/server/header/XContentTypeOptionsServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/XContentTypeOptionsServerHttpHeadersWriterTests.java index 617bf5592d..0ca008d582 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/XContentTypeOptionsServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/XContentTypeOptionsServerHttpHeadersWriterTests.java @@ -42,7 +42,7 @@ public class XContentTypeOptionsServerHttpHeadersWriterTests { @Test public void writeHeadersWhenNoHeadersThenWriteHeadersForXContentTypeOptionsServerHttpHeadersWriter() { this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(XContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)) .containsOnly(XContentTypeOptionsServerHttpHeadersWriter.NOSNIFF); } @@ -52,7 +52,7 @@ public class XContentTypeOptionsServerHttpHeadersWriterTests { String headerValue = "value"; this.headers.set(XContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, headerValue); this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(XContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)) .containsOnly(headerValue); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/XFrameOptionsServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/XFrameOptionsServerHttpHeadersWriterTests.java index 98ee2e5300..ceedd456d2 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/XFrameOptionsServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/XFrameOptionsServerHttpHeadersWriterTests.java @@ -45,7 +45,7 @@ public class XFrameOptionsServerHttpHeadersWriterTests { public void writeHeadersWhenUsingDefaultsThenWritesDeny() { this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS)).containsOnly("DENY"); } @@ -54,7 +54,7 @@ public class XFrameOptionsServerHttpHeadersWriterTests { this.writer.setMode(XFrameOptionsServerHttpHeadersWriter.Mode.DENY); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS)).containsOnly("DENY"); } @@ -63,7 +63,7 @@ public class XFrameOptionsServerHttpHeadersWriterTests { this.writer.setMode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS)).containsOnly("SAMEORIGIN"); } @@ -73,7 +73,7 @@ public class XFrameOptionsServerHttpHeadersWriterTests { this.exchange.getResponse().getHeaders().set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, headerValue); this.writer.writeHttpHeaders(this.exchange); HttpHeaders headers = this.exchange.getResponse().getHeaders(); - assertThat(headers).hasSize(1); + assertThat(headers.headerNames()).hasSize(1); assertThat(headers.get(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS)).containsOnly(headerValue); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/XXssProtectionServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/XXssProtectionServerHttpHeadersWriterTests.java index 1b3d882656..e22a308f41 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/XXssProtectionServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/XXssProtectionServerHttpHeadersWriterTests.java @@ -47,7 +47,7 @@ public class XXssProtectionServerHttpHeadersWriterTests { @Test public void writeHeadersWhenNoHeadersThenWriteHeaders() { this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)).containsOnly("0"); } @@ -56,7 +56,7 @@ public class XXssProtectionServerHttpHeadersWriterTests { String headerValue = "value"; this.headers.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, headerValue); this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)).containsOnly(headerValue); } @@ -64,7 +64,7 @@ public class XXssProtectionServerHttpHeadersWriterTests { void writeHeadersWhenDisabledThenWriteHeaders() { this.writer.setHeaderValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED); this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)).containsOnly("0"); } @@ -72,7 +72,7 @@ public class XXssProtectionServerHttpHeadersWriterTests { void writeHeadersWhenEnabledThenWriteHeaders() { this.writer.setHeaderValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED); this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)).containsOnly("1"); } @@ -80,7 +80,7 @@ public class XXssProtectionServerHttpHeadersWriterTests { void writeHeadersWhenEnabledModeBlockThenWriteHeaders() { this.writer.setHeaderValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED_MODE_BLOCK); this.writer.writeHttpHeaders(this.exchange); - assertThat(this.headers).hasSize(1); + assertThat(this.headers.headerNames()).hasSize(1); assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)) .containsOnly("1; mode=block"); } From a80592a7079a06670a11e5c179c188c4771a2917 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 11:43:53 -0500 Subject: [PATCH 016/149] Use commons-logging directly Closes gh-17061 --- .../classpath/CheckClasspathForProhibitedDependencies.java | 3 --- crypto/spring-security-crypto.gradle | 1 - 2 files changed, 4 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/gradle/classpath/CheckClasspathForProhibitedDependencies.java b/buildSrc/src/main/java/org/springframework/gradle/classpath/CheckClasspathForProhibitedDependencies.java index 4738135e90..20036418d6 100644 --- a/buildSrc/src/main/java/org/springframework/gradle/classpath/CheckClasspathForProhibitedDependencies.java +++ b/buildSrc/src/main/java/org/springframework/gradle/classpath/CheckClasspathForProhibitedDependencies.java @@ -81,9 +81,6 @@ public class CheckClasspathForProhibitedDependencies extends DefaultTask { if (group.startsWith("javax")) { return true; } - if (group.equals("commons-logging")) { - return true; - } if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) { return true; } diff --git a/crypto/spring-security-crypto.gradle b/crypto/spring-security-crypto.gradle index 78a8fd31d0..2b010da16a 100644 --- a/crypto/spring-security-crypto.gradle +++ b/crypto/spring-security-crypto.gradle @@ -2,7 +2,6 @@ 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' From e599a17303b0a4b34400944a56829c9799dd218d Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 13:40:06 -0500 Subject: [PATCH 017/149] Update SNAPSHOT tests Issueh gh-17047 --- .github/workflows/continuous-integration-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 23cdae4f8c..51d2df3b34 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -39,7 +39,7 @@ jobs: toolchain: 17 with: java-version: ${{ matrix.java-version }} - test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=6.2.+ -PreactorVersion=2023.0.+ -PspringDataVersion=2024.0.+ --stacktrace + test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace secrets: inherit check-samples: name: Check Samples From 866d8d0eab40f251770212eae1e248d6e07c9dd7 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 6 May 2025 13:42:13 -0500 Subject: [PATCH 018/149] Disable samples build Spring Boot doesn't support Framework 7 yet Issue gh-17047 --- .../continuous-integration-workflow.yml | 29 ++----------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 51d2df3b34..f8dd0d5584 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -41,46 +41,23 @@ jobs: java-version: ${{ matrix.java-version }} test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace secrets: inherit - check-samples: - name: Check Samples - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'spring-projects' }} - steps: - - uses: actions/checkout@v4 - - name: Set up gradle - uses: spring-io/spring-gradle-build-action@v2 - with: - java-version: 17 - distribution: temurin - - name: Check samples project - env: - LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos - SAMPLES_DIR: ../spring-security-samples - run: | - # Extract version from gradle.properties - version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}') - # Extract samplesBranch from gradle.properties - samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}') - ./gradlew publishMavenJavaPublicationToLocalRepository - ./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR" - ./gradlew --refresh-dependencies --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" test integrationTest deploy-artifacts: name: Deploy Artifacts - needs: [ build, test, check-samples ] + needs: [ build, test] uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1 with: should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }} secrets: inherit deploy-docs: name: Deploy Docs - needs: [ build, test, check-samples ] + needs: [ build, test ] uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1 with: should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }} secrets: inherit deploy-schema: name: Deploy Schema - needs: [ build, test, check-samples ] + needs: [ build, test ] uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1 with: should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }} From e61544c3976b92b0c7ba132120da291e8e4f8f42 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Fri, 18 Apr 2025 17:07:17 +0800 Subject: [PATCH 019/149] Improve OAuth2ResourceServerConfigurer to eliminate deprecated operations Signed-off-by: Yanming Zhou --- .../resource/OAuth2ResourceServerConfigurer.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index e9a425d46d..f5ed36f790 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java @@ -138,6 +138,7 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy; * @author Josh Cummings * @author Evgeniy Cheban * @author Jerome Wacongne <ch4mp@c4-soft.com> + * @author Yanming Zhou * @since 5.1 * @see BearerTokenAuthenticationFilter * @see JwtAuthenticationProvider @@ -490,8 +491,10 @@ public final class OAuth2ResourceServerConfigurer new SpringOpaqueTokenIntrospector(this.introspectionUri, this.clientId, - this.clientSecret); + this.introspector = () -> SpringOpaqueTokenIntrospector.withIntrospectionUri(this.introspectionUri) + .clientId(this.clientId) + .clientSecret(this.clientSecret) + .build(); return this; } @@ -500,8 +503,10 @@ public final class OAuth2ResourceServerConfigurer new SpringOpaqueTokenIntrospector(this.introspectionUri, this.clientId, - this.clientSecret); + this.introspector = () -> SpringOpaqueTokenIntrospector.withIntrospectionUri(this.introspectionUri) + .clientId(this.clientId) + .clientSecret(this.clientSecret) + .build(); return this; } From de622d108254d79708c725102aec607896c60283 Mon Sep 17 00:00:00 2001 From: Shenker93 Date: Sat, 24 Feb 2024 09:30:28 +0200 Subject: [PATCH 020/149] Improve JdbcUserDetailsManager.userExists method --- .../provisioning/JdbcUserDetailsManager.java | 13 +++++++------ .../provisioning/JdbcUserDetailsManagerTests.java | 5 +++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java index 59c0147da5..5856580cce 100644 --- a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java +++ b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java @@ -77,7 +77,7 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa public static final String DEF_DELETE_USER_AUTHORITIES_SQL = "delete from authorities where username = ?"; - public static final String DEF_USER_EXISTS_SQL = "select username from users where username = ?"; + public static final String DEF_USER_EXISTS_SQL = "select count(*) from users where username = ?"; public static final String DEF_CHANGE_PASSWORD_SQL = "update users set password = ? where username = ?"; @@ -337,12 +337,13 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa @Override public boolean userExists(String username) { - List users = requireJdbcTemplate().queryForList(this.userExistsSql, String.class, username); - if (users.size() > 1) { - throw new IncorrectResultSizeDataAccessException("More than one user found with name '" + username + "'", - 1); + @SuppressWarnings("ConstantConditions") + int usersCount = getJdbcTemplate().queryForObject(this.userExistsSql, Integer.class, username); + if (usersCount > 1) { + throw new IncorrectResultSizeDataAccessException( + "[" + usersCount + "] users found with name '" + username + "', expected 1", 1); } - return users.size() == 1; + return usersCount == 1; } @Override diff --git a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java index fb89d191f1..5c8ed72b18 100644 --- a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java +++ b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java @@ -189,6 +189,11 @@ public class JdbcUserDetailsManagerTests { assertThat(this.manager.userExists("joe")).isFalse(); } + @Test + public void userExistsReturnsFalseForNullUsername() { + assertThat(this.manager.userExists(null)).isFalse(); + } + @Test public void userExistsReturnsTrueForExistingUsername() { insertJoe(); From 7fda87aecd01d9628eb89be1e06b0c92ef9bf399 Mon Sep 17 00:00:00 2001 From: milaneuh Date: Tue, 14 Nov 2023 15:36:53 +0100 Subject: [PATCH 021/149] Remove deprecated methods from CookieServerCsrfTokenRepository --- .../web/csrf/CookieCsrfTokenRepository.java | 36 ------------------- .../csrf/CookieCsrfTokenRepositoryTests.java | 14 ++++---- 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java index 546ccd94d0..da7f0f0939 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java @@ -169,14 +169,6 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository { this.cookieName = cookieName; } - /** - * @deprecated Use {@link #setCookieCustomizer(Consumer)} instead. - */ - @Deprecated(since = "6.1") - public void setCookieHttpOnly(boolean cookieHttpOnly) { - this.cookieHttpOnly = cookieHttpOnly; - } - private String getRequestContext(HttpServletRequest request) { String contextPath = request.getContextPath(); return (contextPath.length() > 0) ? contextPath : "/"; @@ -230,32 +222,4 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository { return this.cookiePath; } - /** - * @since 5.2 - * @deprecated Use {@link #setCookieCustomizer(Consumer)} instead. - */ - @Deprecated(since = "6.1") - public void setCookieDomain(String cookieDomain) { - this.cookieDomain = cookieDomain; - } - - /** - * @since 5.4 - * @deprecated Use {@link #setCookieCustomizer(Consumer)} instead. - */ - @Deprecated(since = "6.1") - public void setSecure(Boolean secure) { - this.secure = secure; - } - - /** - * @since 5.5 - * @deprecated Use {@link #setCookieCustomizer(Consumer)} instead. - */ - @Deprecated(since = "6.1") - public void setCookieMaxAge(int cookieMaxAge) { - Assert.isTrue(cookieMaxAge != 0, "cookieMaxAge cannot be zero"); - this.cookieMaxAge = cookieMaxAge; - } - } diff --git a/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java b/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java index bc2088e53c..1f88a2042a 100644 --- a/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java +++ b/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java @@ -112,7 +112,7 @@ class CookieCsrfTokenRepositoryTests { @Test void saveTokenSecureFlagTrue() { this.request.setSecure(false); - this.repository.setSecure(Boolean.TRUE); + this.repository.setCookieCustomizer((cookie)-> cookie.secure(Boolean.TRUE)); CsrfToken token = this.repository.generateToken(this.request); this.repository.saveToken(token, this.request, this.response); Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME); @@ -132,7 +132,7 @@ class CookieCsrfTokenRepositoryTests { @Test void saveTokenSecureFlagFalse() { this.request.setSecure(true); - this.repository.setSecure(Boolean.FALSE); + this.repository.setCookieCustomizer((cookie)-> cookie.secure(Boolean.FALSE)); CsrfToken token = this.repository.generateToken(this.request); this.repository.saveToken(token, this.request, this.response); Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME); @@ -163,7 +163,7 @@ class CookieCsrfTokenRepositoryTests { @Test void saveTokenHttpOnlyTrue() { - this.repository.setCookieHttpOnly(true); + this.repository.setCookieCustomizer((cookie) -> cookie.httpOnly(true)); CsrfToken token = this.repository.generateToken(this.request); this.repository.saveToken(token, this.request, this.response); Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME); @@ -181,7 +181,7 @@ class CookieCsrfTokenRepositoryTests { @Test void saveTokenHttpOnlyFalse() { - this.repository.setCookieHttpOnly(false); + this.repository.setCookieCustomizer((cookie) -> cookie.httpOnly(false)); CsrfToken token = this.repository.generateToken(this.request); this.repository.saveToken(token, this.request, this.response); Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME); @@ -239,7 +239,7 @@ class CookieCsrfTokenRepositoryTests { @Test void saveTokenWithCookieDomain() { String domainName = "example.com"; - this.repository.setCookieDomain(domainName); + this.repository.setCookieCustomizer((cookie) -> cookie.domain(domainName)); CsrfToken token = this.repository.generateToken(this.request); this.repository.saveToken(token, this.request, this.response); Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME); @@ -259,7 +259,7 @@ class CookieCsrfTokenRepositoryTests { @Test void saveTokenWithCookieMaxAge() { int maxAge = 1200; - this.repository.setCookieMaxAge(maxAge); + this.repository.setCookieCustomizer((cookie) -> cookie.maxAge(maxAge)); CsrfToken token = this.repository.generateToken(this.request); this.repository.saveToken(token, this.request, this.response); Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME); @@ -506,7 +506,7 @@ class CookieCsrfTokenRepositoryTests { @Test void setCookieMaxAgeZeroIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCookieMaxAge(0)); + assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCookieCustomizer((cookie) -> cookie.maxAge(0))); } } From 5f833fa2364fa3d874286b7a8d3a435762ca26a9 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 7 May 2025 10:19:05 -0500 Subject: [PATCH 022/149] Fix Checkstyle Errors --- .../web/csrf/CookieCsrfTokenRepositoryTests.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java b/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java index 1f88a2042a..2c870332f1 100644 --- a/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java +++ b/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java @@ -112,7 +112,7 @@ class CookieCsrfTokenRepositoryTests { @Test void saveTokenSecureFlagTrue() { this.request.setSecure(false); - this.repository.setCookieCustomizer((cookie)-> cookie.secure(Boolean.TRUE)); + this.repository.setCookieCustomizer((cookie) -> cookie.secure(Boolean.TRUE)); CsrfToken token = this.repository.generateToken(this.request); this.repository.saveToken(token, this.request, this.response); Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME); @@ -132,7 +132,7 @@ class CookieCsrfTokenRepositoryTests { @Test void saveTokenSecureFlagFalse() { this.request.setSecure(true); - this.repository.setCookieCustomizer((cookie)-> cookie.secure(Boolean.FALSE)); + this.repository.setCookieCustomizer((cookie) -> cookie.secure(Boolean.FALSE)); CsrfToken token = this.repository.generateToken(this.request); this.repository.saveToken(token, this.request, this.response); Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME); @@ -504,9 +504,4 @@ class CookieCsrfTokenRepositoryTests { assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setHeaderName(null)); } - @Test - void setCookieMaxAgeZeroIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCookieCustomizer((cookie) -> cookie.maxAge(0))); - } - } From 2b22cf287788e9a0b48c241d7c31770d2dbe1474 Mon Sep 17 00:00:00 2001 From: Ferdinand Jacobs Date: Mon, 25 Nov 2024 18:49:52 +0100 Subject: [PATCH 023/149] Replace BouncyCastle's deprecated AESFastEngine with the default AESEngine - Update AESEngine to use the default AES engine, following BouncyCastle's recommendations (see release-1-56 of changelog: https://www.bouncycastle.org/download/bouncy-castle-java/?filter=java%3Drelease-1-56). - Migrate to the latest API 'newInstance()' method to allow removal of @SuppressWarnings("deprecation") - Remove @SuppressWarnings("deprecation") --- .../crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java | 7 +++---- .../crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java index 3aa6e6eab3..3fde93d5ea 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java @@ -18,6 +18,7 @@ package org.springframework.security.crypto.encrypt; import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.modes.CBCBlockCipher; import org.bouncycastle.crypto.paddings.PKCS7Padding; import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; @@ -45,23 +46,21 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp } @Override - @SuppressWarnings("deprecation") public byte[] encrypt(byte[] bytes) { byte[] iv = this.ivGenerator.generateKey(); PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher( - new CBCBlockCipher(new org.bouncycastle.crypto.engines.AESFastEngine()), new PKCS7Padding()); + CBCBlockCipher.newInstance(AESEngine.newInstance()), new PKCS7Padding()); blockCipher.init(true, new ParametersWithIV(this.secretKey, iv)); byte[] encrypted = process(blockCipher, bytes); return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted; } @Override - @SuppressWarnings("deprecation") public byte[] decrypt(byte[] encryptedBytes) { byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length); PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher( - new CBCBlockCipher(new org.bouncycastle.crypto.engines.AESFastEngine()), new PKCS7Padding()); + CBCBlockCipher.newInstance(AESEngine.newInstance()), new PKCS7Padding()); blockCipher.init(false, new ParametersWithIV(this.secretKey, iv)); return process(blockCipher, encryptedBytes); } diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java index cce6dd6d99..20ccf9265e 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java @@ -17,6 +17,7 @@ package org.springframework.security.crypto.encrypt; import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.modes.AEADBlockCipher; import org.bouncycastle.crypto.modes.GCMBlockCipher; import org.bouncycastle.crypto.params.AEADParameters; @@ -44,21 +45,19 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp } @Override - @SuppressWarnings("deprecation") public byte[] encrypt(byte[] bytes) { byte[] iv = this.ivGenerator.generateKey(); - GCMBlockCipher blockCipher = new GCMBlockCipher(new org.bouncycastle.crypto.engines.AESFastEngine()); + GCMBlockCipher blockCipher = (GCMBlockCipher) GCMBlockCipher.newInstance(AESEngine.newInstance()); blockCipher.init(true, new AEADParameters(this.secretKey, 128, iv, null)); byte[] encrypted = process(blockCipher, bytes); return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted; } @Override - @SuppressWarnings("deprecation") public byte[] decrypt(byte[] encryptedBytes) { byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length); - GCMBlockCipher blockCipher = new GCMBlockCipher(new org.bouncycastle.crypto.engines.AESFastEngine()); + GCMBlockCipher blockCipher = (GCMBlockCipher) GCMBlockCipher.newInstance(AESEngine.newInstance()); blockCipher.init(false, new AEADParameters(this.secretKey, 128, iv, null)); return process(blockCipher, encryptedBytes); } From 5eb232cd3d0d09a7eac83e24a265079380f25647 Mon Sep 17 00:00:00 2001 From: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com> Date: Mon, 3 Feb 2025 12:33:00 -0600 Subject: [PATCH 024/149] Polish gh-16164 --- .../BouncyCastleAesCbcBytesEncryptor.java | 29 +++++-- .../BouncyCastleAesGcmBytesEncryptor.java | 25 +++++- ...stleAesBytesEncryptorEquivalencyTests.java | 85 ++++++++++++++++++- 3 files changed, 129 insertions(+), 10 deletions(-) diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java index 3fde93d5ea..362f28c255 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,14 @@ package org.springframework.security.crypto.encrypt; +import java.util.function.Supplier; + import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.engines.AESFastEngine; import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.modes.CBCModeCipher; import org.bouncycastle.crypto.paddings.PKCS7Padding; import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.bouncycastle.crypto.params.ParametersWithIV; @@ -37,6 +41,8 @@ import org.springframework.security.crypto.util.EncodingUtils; */ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryptor { + private Supplier cipherFactory = () -> CBCBlockCipher.newInstance(AESEngine.newInstance()); + public BouncyCastleAesCbcBytesEncryptor(String password, CharSequence salt) { super(password, salt); } @@ -48,8 +54,8 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp @Override public byte[] encrypt(byte[] bytes) { byte[] iv = this.ivGenerator.generateKey(); - PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher( - CBCBlockCipher.newInstance(AESEngine.newInstance()), new PKCS7Padding()); + PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(this.cipherFactory.get(), + new PKCS7Padding()); blockCipher.init(true, new ParametersWithIV(this.secretKey, iv)); byte[] encrypted = process(blockCipher, bytes); return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted; @@ -59,8 +65,8 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp public byte[] decrypt(byte[] encryptedBytes) { byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length); - PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher( - CBCBlockCipher.newInstance(AESEngine.newInstance()), new PKCS7Padding()); + PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(this.cipherFactory.get(), + new PKCS7Padding()); blockCipher.init(false, new ParametersWithIV(this.secretKey, iv)); return process(blockCipher, encryptedBytes); } @@ -82,4 +88,17 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp return out; } + /** + * Used to test compatibility with deprecated {@link AESFastEngine}. + */ + @SuppressWarnings("deprecation") + static BouncyCastleAesCbcBytesEncryptor withAESFastEngine(String password, CharSequence salt, + BytesKeyGenerator ivGenerator) { + BouncyCastleAesCbcBytesEncryptor bytesEncryptor = new BouncyCastleAesCbcBytesEncryptor(password, salt, + ivGenerator); + bytesEncryptor.cipherFactory = () -> new CBCBlockCipher(new AESFastEngine()); + + return bytesEncryptor; + } + } diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java index 20ccf9265e..52d6e6cc48 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,11 @@ package org.springframework.security.crypto.encrypt; +import java.util.function.Supplier; + import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.engines.AESFastEngine; import org.bouncycastle.crypto.modes.AEADBlockCipher; import org.bouncycastle.crypto.modes.GCMBlockCipher; import org.bouncycastle.crypto.params.AEADParameters; @@ -36,6 +39,9 @@ import org.springframework.security.crypto.util.EncodingUtils; */ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryptor { + private Supplier cipherFactory = () -> (GCMBlockCipher) GCMBlockCipher + .newInstance(AESEngine.newInstance()); + public BouncyCastleAesGcmBytesEncryptor(String password, CharSequence salt) { super(password, salt); } @@ -47,7 +53,7 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp @Override public byte[] encrypt(byte[] bytes) { byte[] iv = this.ivGenerator.generateKey(); - GCMBlockCipher blockCipher = (GCMBlockCipher) GCMBlockCipher.newInstance(AESEngine.newInstance()); + AEADBlockCipher blockCipher = this.cipherFactory.get(); blockCipher.init(true, new AEADParameters(this.secretKey, 128, iv, null)); byte[] encrypted = process(blockCipher, bytes); return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted; @@ -57,7 +63,7 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp public byte[] decrypt(byte[] encryptedBytes) { byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length); - GCMBlockCipher blockCipher = (GCMBlockCipher) GCMBlockCipher.newInstance(AESEngine.newInstance()); + AEADBlockCipher blockCipher = this.cipherFactory.get(); blockCipher.init(false, new AEADParameters(this.secretKey, 128, iv, null)); return process(blockCipher, encryptedBytes); } @@ -79,4 +85,17 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp return out; } + /** + * Used to test compatibility with deprecated {@link AESFastEngine}. + */ + @SuppressWarnings("deprecation") + static BouncyCastleAesGcmBytesEncryptor withAESFastEngine(String password, CharSequence salt, + BytesKeyGenerator ivGenerator) { + BouncyCastleAesGcmBytesEncryptor bytesEncryptor = new BouncyCastleAesGcmBytesEncryptor(password, salt, + ivGenerator); + bytesEncryptor.cipherFactory = () -> new GCMBlockCipher(new AESFastEngine()); + + return bytesEncryptor; + } + } diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTests.java index a37c83092c..0dda6ebfc5 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTests.java +++ b/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2021 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,14 @@ package org.springframework.security.crypto.encrypt; import java.security.SecureRandom; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.Random; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.springframework.security.crypto.codec.Hex; @@ -89,6 +93,64 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTests { testCompatibility(bcEncryptor, jceEncryptor); } + @Test + public void bouncyCastleAesGcmWithAESFastEngineCompatible() throws Exception { + CryptoAssumptions.assumeGCMJCE(); + BytesEncryptor fastEngineEncryptor = BouncyCastleAesGcmBytesEncryptor.withAESFastEngine(this.password, + this.salt, KeyGenerators.secureRandom(16)); + BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesGcmBytesEncryptor(this.password, this.salt, + KeyGenerators.secureRandom(16)); + testCompatibility(fastEngineEncryptor, defaultEngineEncryptor); + } + + @Test + public void bouncyCastleAesCbcWithAESFastEngineCompatible() throws Exception { + CryptoAssumptions.assumeCBCJCE(); + BytesEncryptor fastEngineEncryptor = BouncyCastleAesCbcBytesEncryptor.withAESFastEngine(this.password, + this.salt, KeyGenerators.secureRandom(16)); + BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesCbcBytesEncryptor(this.password, this.salt, + KeyGenerators.secureRandom(16)); + testCompatibility(fastEngineEncryptor, defaultEngineEncryptor); + } + + /** + * Comment out @Disabled below to compare relative speed of deprecated AESFastEngine + * with the default AESEngine. + */ + @Disabled + @RepeatedTest(100) + public void bouncyCastleAesGcmWithAESFastEngineSpeedTest() throws Exception { + CryptoAssumptions.assumeGCMJCE(); + BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesGcmBytesEncryptor(this.password, this.salt, + KeyGenerators.secureRandom(16)); + BytesEncryptor fastEngineEncryptor = BouncyCastleAesGcmBytesEncryptor.withAESFastEngine(this.password, + this.salt, KeyGenerators.secureRandom(16)); + long defaultNanos = testSpeed(defaultEngineEncryptor); + long fastNanos = testSpeed(fastEngineEncryptor); + System.out.println(nanosToReadableString("AES GCM w/Default Engine", defaultNanos)); + System.out.println(nanosToReadableString("AES GCM w/ Fast Engine", fastNanos)); + assertThat(fastNanos).isLessThan(defaultNanos); + } + + /** + * Comment out @Disabled below to compare relative speed of deprecated AESFastEngine + * with the default AESEngine. + */ + @Disabled + @RepeatedTest(100) + public void bouncyCastleAesCbcWithAESFastEngineSpeedTest() throws Exception { + CryptoAssumptions.assumeCBCJCE(); + BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesCbcBytesEncryptor(this.password, this.salt, + KeyGenerators.secureRandom(16)); + BytesEncryptor fastEngineEncryptor = BouncyCastleAesCbcBytesEncryptor.withAESFastEngine(this.password, + this.salt, KeyGenerators.secureRandom(16)); + long defaultNanos = testSpeed(defaultEngineEncryptor); + long fastNanos = testSpeed(fastEngineEncryptor); + System.out.println(nanosToReadableString("AES CBC w/Default Engine", defaultNanos)); + System.out.println(nanosToReadableString("AES CBC w/ Fast Engine", fastNanos)); + assertThat(fastNanos).isLessThan(defaultNanos); + } + private void testEquivalence(BytesEncryptor left, BytesEncryptor right) { for (int size = 1; size < 2048; size++) { this.testData = new byte[size]; @@ -107,7 +169,7 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTests { private void testCompatibility(BytesEncryptor left, BytesEncryptor right) { // tests that right can decrypt what left encrypted and vice versa - // and that the decypted data is the same as the original + // and that the decrypted data is the same as the original for (int size = 1; size < 2048; size++) { this.testData = new byte[size]; this.secureRandom.nextBytes(this.testData); @@ -120,6 +182,25 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTests { } } + private long testSpeed(BytesEncryptor bytesEncryptor) { + long start = System.nanoTime(); + for (int size = 0; size < 2048; size++) { + this.testData = new byte[size]; + this.secureRandom.nextBytes(this.testData); + byte[] encrypted = bytesEncryptor.encrypt(this.testData); + byte[] decrypted = bytesEncryptor.decrypt(encrypted); + assertThat(decrypted).containsExactly(this.testData); + } + return System.nanoTime() - start; + } + + private String nanosToReadableString(String label, long nanos) { + Duration duration = Duration.ofNanos(nanos); + Duration millis = duration.truncatedTo(ChronoUnit.MILLIS); + Duration micros = duration.minus(millis).dividedBy(1000); + return "%s: %dms %dμs".formatted(label, duration.toMillis(), micros.toNanos()); + } + /** * A BytesKeyGenerator that always generates the same sequence of values */ From d52289bd7a27acae736a44737ff636ab21a2d2ca Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 7 May 2025 10:54:55 -0500 Subject: [PATCH 025/149] Remove Unnecessary Backwards Compatability Since this is going to be merged into Spring Security 7 (a major release) and AESFastEngine is deprecated, we should no longer support it (as it will likely be removed from Bouncy Castle) --- .../BouncyCastleAesCbcBytesEncryptor.java | 26 ++------ .../BouncyCastleAesGcmBytesEncryptor.java | 23 +------ ...stleAesBytesEncryptorEquivalencyTests.java | 60 ------------------- 3 files changed, 6 insertions(+), 103 deletions(-) diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java index 362f28c255..565ec4d3c1 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java @@ -16,12 +16,9 @@ package org.springframework.security.crypto.encrypt; -import java.util.function.Supplier; - import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.engines.AESFastEngine; import org.bouncycastle.crypto.modes.CBCBlockCipher; import org.bouncycastle.crypto.modes.CBCModeCipher; import org.bouncycastle.crypto.paddings.PKCS7Padding; @@ -41,8 +38,6 @@ import org.springframework.security.crypto.util.EncodingUtils; */ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryptor { - private Supplier cipherFactory = () -> CBCBlockCipher.newInstance(AESEngine.newInstance()); - public BouncyCastleAesCbcBytesEncryptor(String password, CharSequence salt) { super(password, salt); } @@ -54,8 +49,8 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp @Override public byte[] encrypt(byte[] bytes) { byte[] iv = this.ivGenerator.generateKey(); - PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(this.cipherFactory.get(), - new PKCS7Padding()); + CBCModeCipher cbcModeCipher = CBCBlockCipher.newInstance(AESEngine.newInstance()); + PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(cbcModeCipher, new PKCS7Padding()); blockCipher.init(true, new ParametersWithIV(this.secretKey, iv)); byte[] encrypted = process(blockCipher, bytes); return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted; @@ -63,10 +58,10 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp @Override public byte[] decrypt(byte[] encryptedBytes) { + CBCModeCipher cbcModeCipher = CBCBlockCipher.newInstance(AESEngine.newInstance()); byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length); - PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(this.cipherFactory.get(), - new PKCS7Padding()); + PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(cbcModeCipher, new PKCS7Padding()); blockCipher.init(false, new ParametersWithIV(this.secretKey, iv)); return process(blockCipher, encryptedBytes); } @@ -88,17 +83,4 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp return out; } - /** - * Used to test compatibility with deprecated {@link AESFastEngine}. - */ - @SuppressWarnings("deprecation") - static BouncyCastleAesCbcBytesEncryptor withAESFastEngine(String password, CharSequence salt, - BytesKeyGenerator ivGenerator) { - BouncyCastleAesCbcBytesEncryptor bytesEncryptor = new BouncyCastleAesCbcBytesEncryptor(password, salt, - ivGenerator); - bytesEncryptor.cipherFactory = () -> new CBCBlockCipher(new AESFastEngine()); - - return bytesEncryptor; - } - } diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java index 52d6e6cc48..15eb61ad70 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java @@ -16,11 +16,8 @@ package org.springframework.security.crypto.encrypt; -import java.util.function.Supplier; - import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.engines.AESFastEngine; import org.bouncycastle.crypto.modes.AEADBlockCipher; import org.bouncycastle.crypto.modes.GCMBlockCipher; import org.bouncycastle.crypto.params.AEADParameters; @@ -39,9 +36,6 @@ import org.springframework.security.crypto.util.EncodingUtils; */ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryptor { - private Supplier cipherFactory = () -> (GCMBlockCipher) GCMBlockCipher - .newInstance(AESEngine.newInstance()); - public BouncyCastleAesGcmBytesEncryptor(String password, CharSequence salt) { super(password, salt); } @@ -53,7 +47,7 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp @Override public byte[] encrypt(byte[] bytes) { byte[] iv = this.ivGenerator.generateKey(); - AEADBlockCipher blockCipher = this.cipherFactory.get(); + AEADBlockCipher blockCipher = GCMBlockCipher.newInstance(AESEngine.newInstance()); blockCipher.init(true, new AEADParameters(this.secretKey, 128, iv, null)); byte[] encrypted = process(blockCipher, bytes); return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted; @@ -63,7 +57,7 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp public byte[] decrypt(byte[] encryptedBytes) { byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length); - AEADBlockCipher blockCipher = this.cipherFactory.get(); + AEADBlockCipher blockCipher = GCMBlockCipher.newInstance(AESEngine.newInstance()); blockCipher.init(false, new AEADParameters(this.secretKey, 128, iv, null)); return process(blockCipher, encryptedBytes); } @@ -85,17 +79,4 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp return out; } - /** - * Used to test compatibility with deprecated {@link AESFastEngine}. - */ - @SuppressWarnings("deprecation") - static BouncyCastleAesGcmBytesEncryptor withAESFastEngine(String password, CharSequence salt, - BytesKeyGenerator ivGenerator) { - BouncyCastleAesGcmBytesEncryptor bytesEncryptor = new BouncyCastleAesGcmBytesEncryptor(password, salt, - ivGenerator); - bytesEncryptor.cipherFactory = () -> new GCMBlockCipher(new AESFastEngine()); - - return bytesEncryptor; - } - } diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTests.java index 0dda6ebfc5..7db08b3378 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTests.java +++ b/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTests.java @@ -23,8 +23,6 @@ import java.util.Random; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.springframework.security.crypto.codec.Hex; @@ -93,64 +91,6 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTests { testCompatibility(bcEncryptor, jceEncryptor); } - @Test - public void bouncyCastleAesGcmWithAESFastEngineCompatible() throws Exception { - CryptoAssumptions.assumeGCMJCE(); - BytesEncryptor fastEngineEncryptor = BouncyCastleAesGcmBytesEncryptor.withAESFastEngine(this.password, - this.salt, KeyGenerators.secureRandom(16)); - BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesGcmBytesEncryptor(this.password, this.salt, - KeyGenerators.secureRandom(16)); - testCompatibility(fastEngineEncryptor, defaultEngineEncryptor); - } - - @Test - public void bouncyCastleAesCbcWithAESFastEngineCompatible() throws Exception { - CryptoAssumptions.assumeCBCJCE(); - BytesEncryptor fastEngineEncryptor = BouncyCastleAesCbcBytesEncryptor.withAESFastEngine(this.password, - this.salt, KeyGenerators.secureRandom(16)); - BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesCbcBytesEncryptor(this.password, this.salt, - KeyGenerators.secureRandom(16)); - testCompatibility(fastEngineEncryptor, defaultEngineEncryptor); - } - - /** - * Comment out @Disabled below to compare relative speed of deprecated AESFastEngine - * with the default AESEngine. - */ - @Disabled - @RepeatedTest(100) - public void bouncyCastleAesGcmWithAESFastEngineSpeedTest() throws Exception { - CryptoAssumptions.assumeGCMJCE(); - BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesGcmBytesEncryptor(this.password, this.salt, - KeyGenerators.secureRandom(16)); - BytesEncryptor fastEngineEncryptor = BouncyCastleAesGcmBytesEncryptor.withAESFastEngine(this.password, - this.salt, KeyGenerators.secureRandom(16)); - long defaultNanos = testSpeed(defaultEngineEncryptor); - long fastNanos = testSpeed(fastEngineEncryptor); - System.out.println(nanosToReadableString("AES GCM w/Default Engine", defaultNanos)); - System.out.println(nanosToReadableString("AES GCM w/ Fast Engine", fastNanos)); - assertThat(fastNanos).isLessThan(defaultNanos); - } - - /** - * Comment out @Disabled below to compare relative speed of deprecated AESFastEngine - * with the default AESEngine. - */ - @Disabled - @RepeatedTest(100) - public void bouncyCastleAesCbcWithAESFastEngineSpeedTest() throws Exception { - CryptoAssumptions.assumeCBCJCE(); - BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesCbcBytesEncryptor(this.password, this.salt, - KeyGenerators.secureRandom(16)); - BytesEncryptor fastEngineEncryptor = BouncyCastleAesCbcBytesEncryptor.withAESFastEngine(this.password, - this.salt, KeyGenerators.secureRandom(16)); - long defaultNanos = testSpeed(defaultEngineEncryptor); - long fastNanos = testSpeed(fastEngineEncryptor); - System.out.println(nanosToReadableString("AES CBC w/Default Engine", defaultNanos)); - System.out.println(nanosToReadableString("AES CBC w/ Fast Engine", fastNanos)); - assertThat(fastNanos).isLessThan(defaultNanos); - } - private void testEquivalence(BytesEncryptor left, BytesEncryptor right) { for (int size = 1; size < 2048; size++) { this.testData = new byte[size]; From 23e7c9eeaa9156f488db74d4f1c4f42a5bd1a86c Mon Sep 17 00:00:00 2001 From: kiruthiga1793 Date: Thu, 30 Jan 2025 19:15:43 -0500 Subject: [PATCH 026/149] Add Twitter/X to CommonOAuth2Provider Signed-off-by: kiruthiga1793 --- .../oauth2/client/CommonOAuth2Provider.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java b/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java index c51b716e16..7ffffb3a59 100644 --- a/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java +++ b/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java @@ -87,6 +87,23 @@ public enum CommonOAuth2Provider { }, + X { + + @Override + public Builder getBuilder(String registrationId) { + ClientRegistration.Builder builder = getBuilder(registrationId, + ClientAuthenticationMethod.CLIENT_SECRET_POST, DEFAULT_REDIRECT_URL); + builder.scope("users.read","tweet.read"); + builder.authorizationUri("https://x.com/i/oauth2/authorize"); + builder.tokenUri("https://api.x.com/2/oauth2/token"); + builder.userInfoUri("https://api.x.com/2/users/me"); + builder.userNameAttributeName("username"); + builder.clientName("X"); + return builder; + } + + }, + OKTA { @Override From f13836c9c86d2e4c3bda2f06fe00937a3c8dec0a Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 7 May 2025 11:30:08 -0500 Subject: [PATCH 027/149] Add X to CommonOAuth2Provider Reference Issue gh-16510 Signed-off-by: Rob Winch <362503+rwinch@users.noreply.github.com> --- docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc | 2 +- docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc b/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc index 8ea1642c1b..a98676f846 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc @@ -153,7 +153,7 @@ A `ClientRegistration` can be initially configured using discovery of an OpenID [[webflux-oauth2-login-common-oauth2-provider]] == CommonOAuth2Provider -`CommonOAuth2Provider` pre-defines a set of default client properties for a number of well known providers: Google, GitHub, Facebook, and Okta. +`CommonOAuth2Provider` pre-defines a set of default client properties for a number of well known providers: Google, GitHub, Facebook, X, and Okta. For example, the `authorization-uri`, `token-uri`, and `user-info-uri` do not change often for a Provider. Therefore, it makes sense to provide default values in order to reduce the required configuration. diff --git a/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc b/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc index ffcecee7c9..4c179cc62a 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc @@ -153,7 +153,7 @@ You can initially configure a `ClientRegistration` by using discovery of an Open [[oauth2login-common-oauth2-provider]] == CommonOAuth2Provider -`CommonOAuth2Provider` pre-defines a set of default client properties for a number of well known providers: Google, GitHub, Facebook, and Okta. +`CommonOAuth2Provider` pre-defines a set of default client properties for a number of well known providers: Google, GitHub, Facebook, X, and Okta. For example, the `authorization-uri`, `token-uri`, and `user-info-uri` do not change often for a provider. Therefore, it makes sense to provide default values, to reduce the required configuration. From 693a5beb2407bc9992acb60a7506271ebd94d422 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 7 May 2025 14:55:04 -0500 Subject: [PATCH 028/149] Format CommonOAuth2Provider --- .../security/config/oauth2/client/CommonOAuth2Provider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java b/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java index 7ffffb3a59..96d0e51eac 100644 --- a/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java +++ b/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java @@ -93,7 +93,7 @@ public enum CommonOAuth2Provider { public Builder getBuilder(String registrationId) { ClientRegistration.Builder builder = getBuilder(registrationId, ClientAuthenticationMethod.CLIENT_SECRET_POST, DEFAULT_REDIRECT_URL); - builder.scope("users.read","tweet.read"); + builder.scope("users.read", "tweet.read"); builder.authorizationUri("https://x.com/i/oauth2/authorize"); builder.tokenUri("https://api.x.com/2/oauth2/token"); builder.userInfoUri("https://api.x.com/2/users/me"); From 241c3cd35a286b6127d73ce762db57bced057015 Mon Sep 17 00:00:00 2001 From: M-Faheem-Khan Date: Sun, 27 Apr 2025 23:53:19 -0400 Subject: [PATCH 029/149] Remove deprecated Cookie usage Remove usage of comment and verison usage Signed-off-by: M-Faheem-Khan --- .../AbstractRememberMeServices.java | 3 -- .../web/firewall/FirewalledResponse.java | 1 - .../web/jackson2/CookieDeserializer.java | 2 -- .../web/jackson2/SavedCookieMixin.java | 6 ++-- .../web/savedrequest/SavedCookie.java | 33 +------------------ .../AbstractRememberMeServicesTests.java | 11 ------- .../web/firewall/FirewalledResponseTests.java | 1 - .../DefaultSavedRequestMixinTests.java | 2 -- .../web/jackson2/SavedCookieMixinTests.java | 6 +--- .../web/savedrequest/SavedCookieTests.java | 14 -------- 10 files changed, 4 insertions(+), 75 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java index 30b9e261d0..fd1c38b18a 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java @@ -372,9 +372,6 @@ public abstract class AbstractRememberMeServices if (this.cookieDomain != null) { cookie.setDomain(this.cookieDomain); } - if (maxAge < 1) { - cookie.setVersion(1); - } cookie.setSecure((this.useSecureCookie != null) ? this.useSecureCookie : request.isSecure()); cookie.setHttpOnly(true); diff --git a/web/src/main/java/org/springframework/security/web/firewall/FirewalledResponse.java b/web/src/main/java/org/springframework/security/web/firewall/FirewalledResponse.java index e5d34ff197..12bfacedd3 100644 --- a/web/src/main/java/org/springframework/security/web/firewall/FirewalledResponse.java +++ b/web/src/main/java/org/springframework/security/web/firewall/FirewalledResponse.java @@ -67,7 +67,6 @@ class FirewalledResponse extends HttpServletResponseWrapper { validateCrlf(SET_COOKIE_HEADER, cookie.getValue()); validateCrlf(SET_COOKIE_HEADER, cookie.getPath()); validateCrlf(SET_COOKIE_HEADER, cookie.getDomain()); - validateCrlf(SET_COOKIE_HEADER, cookie.getComment()); } super.addCookie(cookie); } diff --git a/web/src/main/java/org/springframework/security/web/jackson2/CookieDeserializer.java b/web/src/main/java/org/springframework/security/web/jackson2/CookieDeserializer.java index d4123a8188..99b3bc9d1a 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/CookieDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/CookieDeserializer.java @@ -45,11 +45,9 @@ class CookieDeserializer extends JsonDeserializer { ObjectMapper mapper = (ObjectMapper) jp.getCodec(); JsonNode jsonNode = mapper.readTree(jp); Cookie cookie = new Cookie(readJsonNode(jsonNode, "name").asText(), readJsonNode(jsonNode, "value").asText()); - cookie.setComment(readJsonNode(jsonNode, "comment").asText()); cookie.setDomain(readJsonNode(jsonNode, "domain").asText()); cookie.setMaxAge(readJsonNode(jsonNode, "maxAge").asInt(-1)); cookie.setSecure(readJsonNode(jsonNode, "secure").asBoolean()); - cookie.setVersion(readJsonNode(jsonNode, "version").asInt()); cookie.setPath(readJsonNode(jsonNode, "path").asText()); JsonNode attributes = readJsonNode(jsonNode, "attributes"); cookie.setHttpOnly(readJsonNode(attributes, "HttpOnly") != null); diff --git a/web/src/main/java/org/springframework/security/web/jackson2/SavedCookieMixin.java b/web/src/main/java/org/springframework/security/web/jackson2/SavedCookieMixin.java index 3859b3ae24..817972db15 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/SavedCookieMixin.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/SavedCookieMixin.java @@ -44,10 +44,8 @@ abstract class SavedCookieMixin { @JsonCreator SavedCookieMixin(@JsonProperty("name") String name, @JsonProperty("value") String value, - @JsonProperty("comment") String comment, @JsonProperty("domain") String domain, - @JsonProperty("maxAge") int maxAge, @JsonProperty("path") String path, - @JsonProperty("secure") boolean secure, @JsonProperty("version") int version) { - + @JsonProperty("domain") String domain, @JsonProperty("maxAge") int maxAge, + @JsonProperty("path") String path, @JsonProperty("secure") boolean secure) { } } diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/SavedCookie.java b/web/src/main/java/org/springframework/security/web/savedrequest/SavedCookie.java index c4d6dbce33..5f55c16df9 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/SavedCookie.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/SavedCookie.java @@ -35,8 +35,6 @@ public class SavedCookie implements Serializable { private final String value; - private final String comment; - private final String domain; private final int maxAge; @@ -45,28 +43,13 @@ public class SavedCookie implements Serializable { private final boolean secure; - private final int version; - - /** - * @deprecated use - * {@link org.springframework.security.web.savedrequest.SavedCookie#SavedCookie(String, String, String, int, String, boolean)} - * instead - */ - @Deprecated(forRemoval = true, since = "6.1") - public SavedCookie(String name, String value, String comment, String domain, int maxAge, String path, - boolean secure, int version) { + public SavedCookie(String name, String value, String domain, int maxAge, String path, boolean secure) { this.name = name; this.value = value; - this.comment = comment; this.domain = domain; this.maxAge = maxAge; this.path = path; this.secure = secure; - this.version = version; - } - - public SavedCookie(String name, String value, String domain, int maxAge, String path, boolean secure) { - this(name, value, null, domain, maxAge, path, secure, 0); } public SavedCookie(Cookie cookie) { @@ -82,11 +65,6 @@ public class SavedCookie implements Serializable { return this.value; } - @Deprecated(forRemoval = true, since = "6.1") - public String getComment() { - return this.comment; - } - public String getDomain() { return this.domain; } @@ -103,23 +81,14 @@ public class SavedCookie implements Serializable { return this.secure; } - @Deprecated(forRemoval = true, since = "6.1") - public int getVersion() { - return this.version; - } - public Cookie getCookie() { Cookie cookie = new Cookie(getName(), getValue()); - if (getComment() != null) { - cookie.setComment(getComment()); - } if (getDomain() != null) { cookie.setDomain(getDomain()); } if (getPath() != null) { cookie.setPath(getPath()); } - cookie.setVersion(getVersion()); cookie.setMaxAge(getMaxAge()); cookie.setSecure(isSecure()); return cookie; diff --git a/web/src/test/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServicesTests.java b/web/src/test/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServicesTests.java index ba399db5b1..ec541fa7bc 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServicesTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServicesTests.java @@ -362,17 +362,6 @@ public class AbstractRememberMeServicesTests { assertThat(cookie.isHttpOnly()).isTrue(); } - // SEC-2791 - @Test - public void setCookieMaxAge1VersionSet() { - MockRememberMeServices services = new MockRememberMeServices(); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - services.setCookie(new String[] { "value" }, 1, request, response); - Cookie cookie = response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); - assertThat(cookie.getVersion()).isZero(); - } - @Test public void setCookieDomainValue() { MockRememberMeServices services = new MockRememberMeServices(); diff --git a/web/src/test/java/org/springframework/security/web/firewall/FirewalledResponseTests.java b/web/src/test/java/org/springframework/security/web/firewall/FirewalledResponseTests.java index e5d7c03a4b..4db7f46b85 100644 --- a/web/src/test/java/org/springframework/security/web/firewall/FirewalledResponseTests.java +++ b/web/src/test/java/org/springframework/security/web/firewall/FirewalledResponseTests.java @@ -93,7 +93,6 @@ public class FirewalledResponseTests { Cookie cookie = new Cookie("foo", "bar"); cookie.setPath("/foobar"); cookie.setDomain("foobar"); - cookie.setComment("foobar"); this.fwResponse.addCookie(cookie); verify(this.response).addCookie(cookie); } diff --git a/web/src/test/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixinTests.java b/web/src/test/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixinTests.java index 6e7a8bc054..e290e06828 100644 --- a/web/src/test/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixinTests.java +++ b/web/src/test/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixinTests.java @@ -45,11 +45,9 @@ public class DefaultSavedRequestMixinTests extends AbstractMixinTests { + "\"@class\": \"org.springframework.security.web.savedrequest.SavedCookie\", " + "\"name\": \"SESSION\", " + "\"value\": \"123456789\", " - + "\"comment\": null, " + "\"maxAge\": -1, " + "\"path\": null, " + "\"secure\":false, " - + "\"version\": 0, " + "\"domain\": null" + "}]]"; // @formatter:on diff --git a/web/src/test/java/org/springframework/security/web/jackson2/SavedCookieMixinTests.java b/web/src/test/java/org/springframework/security/web/jackson2/SavedCookieMixinTests.java index 2582bddd9a..9374654d14 100644 --- a/web/src/test/java/org/springframework/security/web/jackson2/SavedCookieMixinTests.java +++ b/web/src/test/java/org/springframework/security/web/jackson2/SavedCookieMixinTests.java @@ -42,11 +42,9 @@ public class SavedCookieMixinTests extends AbstractMixinTests { + "\"@class\": \"org.springframework.security.web.savedrequest.SavedCookie\", " + "\"name\": \"SESSION\", " + "\"value\": \"123456789\", " - + "\"comment\": null, " + "\"maxAge\": -1, " + "\"path\": null, " + "\"secure\":false, " - + "\"version\": 0, " + "\"domain\": null" + "}"; // @formatter:on @@ -90,13 +88,11 @@ public class SavedCookieMixinTests extends AbstractMixinTests { @Test public void deserializeSavedCookieJsonTest() throws IOException { - SavedCookie savedCookie = (SavedCookie) this.mapper.readValue(COOKIE_JSON, Object.class); + SavedCookie savedCookie = this.mapper.readValue(COOKIE_JSON, SavedCookie.class); assertThat(savedCookie).isNotNull(); assertThat(savedCookie.getName()).isEqualTo("SESSION"); assertThat(savedCookie.getValue()).isEqualTo("123456789"); assertThat(savedCookie.isSecure()).isEqualTo(false); - assertThat(savedCookie.getVersion()).isZero(); - assertThat(savedCookie.getComment()).isNull(); } } diff --git a/web/src/test/java/org/springframework/security/web/savedrequest/SavedCookieTests.java b/web/src/test/java/org/springframework/security/web/savedrequest/SavedCookieTests.java index 77d0759657..9fdee451d4 100644 --- a/web/src/test/java/org/springframework/security/web/savedrequest/SavedCookieTests.java +++ b/web/src/test/java/org/springframework/security/web/savedrequest/SavedCookieTests.java @@ -33,12 +33,10 @@ public class SavedCookieTests { @BeforeEach public void setUp() { this.cookie = new Cookie("name", "value"); - this.cookie.setComment("comment"); this.cookie.setDomain("domain"); this.cookie.setMaxAge(100); this.cookie.setPath("path"); this.cookie.setSecure(true); - this.cookie.setVersion(11); this.savedCookie = new SavedCookie(this.cookie); } @@ -52,11 +50,6 @@ public class SavedCookieTests { assertThat(this.savedCookie.getValue()).isEqualTo(this.cookie.getValue()); } - @Test - public void testGetComment() { - assertThat(this.savedCookie.getComment()).isEqualTo(this.cookie.getComment()); - } - @Test public void testGetDomain() { assertThat(this.savedCookie.getDomain()).isEqualTo(this.cookie.getDomain()); @@ -72,22 +65,15 @@ public class SavedCookieTests { assertThat(this.savedCookie.getPath()).isEqualTo(this.cookie.getPath()); } - @Test - public void testGetVersion() { - assertThat(this.savedCookie.getVersion()).isEqualTo(this.cookie.getVersion()); - } - @Test public void testGetCookie() { Cookie other = this.savedCookie.getCookie(); - assertThat(other.getComment()).isEqualTo(this.cookie.getComment()); assertThat(other.getDomain()).isEqualTo(this.cookie.getDomain()); assertThat(other.getMaxAge()).isEqualTo(this.cookie.getMaxAge()); assertThat(other.getName()).isEqualTo(this.cookie.getName()); assertThat(other.getPath()).isEqualTo(this.cookie.getPath()); assertThat(other.getSecure()).isEqualTo(this.cookie.getSecure()); assertThat(other.getValue()).isEqualTo(this.cookie.getValue()); - assertThat(other.getVersion()).isEqualTo(this.cookie.getVersion()); } @Test From 6118587ff8f9879a2886194d8899ed6c84538a22 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 7 May 2025 14:38:09 -0500 Subject: [PATCH 030/149] SavedCookieMixinTests uses readValue(String,Object.class) The test should not provide SavedCookie.class to the ObjectMapper since this is not done in production. In particular, it provides the type that it should be deserialized, but this must be provided in the JSON since the type is unknown at the time of deserialization. Issue gh-17006 --- .../security/web/jackson2/SavedCookieMixinTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/test/java/org/springframework/security/web/jackson2/SavedCookieMixinTests.java b/web/src/test/java/org/springframework/security/web/jackson2/SavedCookieMixinTests.java index 9374654d14..1965146a11 100644 --- a/web/src/test/java/org/springframework/security/web/jackson2/SavedCookieMixinTests.java +++ b/web/src/test/java/org/springframework/security/web/jackson2/SavedCookieMixinTests.java @@ -88,7 +88,7 @@ public class SavedCookieMixinTests extends AbstractMixinTests { @Test public void deserializeSavedCookieJsonTest() throws IOException { - SavedCookie savedCookie = this.mapper.readValue(COOKIE_JSON, SavedCookie.class); + SavedCookie savedCookie = (SavedCookie) this.mapper.readValue(COOKIE_JSON, Object.class); assertThat(savedCookie).isNotNull(); assertThat(savedCookie.getName()).isEqualTo("SESSION"); assertThat(savedCookie.getValue()).isEqualTo("123456789"); From 1e4dd713c59814cc9c9252dcf39babcd0c502fc4 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Wed, 7 May 2025 23:35:54 +0700 Subject: [PATCH 031/149] Remove APPLICATION_JSON_UTF8 usage Signed-off-by: Tran Ngoc Nhan --- ...ultOAuth2TokenRequestHeadersConverter.java | 4 +- ...orizationCodeTokenResponseClientTests.java | 5 +-- ...ntCredentialsTokenResponseClientTests.java | 5 +-- ...aultJwtBearerTokenResponseClientTests.java | 5 +-- ...uth2TokenRequestHeadersConverterTests.java | 9 ++--- ...faultPasswordTokenResponseClientTests.java | 5 +-- ...tRefreshTokenTokenResponseClientTests.java | 5 +-- ...TokenExchangeTokenResponseClientTests.java | 14 +++---- ...earerGrantRequestEntityConverterTests.java | 5 +-- ...nCodeGrantRequestEntityConverterTests.java | 7 ++-- ...tialsGrantRequestEntityConverterTests.java | 7 ++-- ...swordGrantRequestEntityConverterTests.java | 5 +-- ...TokenGrantRequestEntityConverterTests.java | 5 +-- ...hangeGrantRequestEntityConverterTests.java | 8 ++-- .../security/http/MediaTypes.java | 39 ------------------- 15 files changed, 34 insertions(+), 94 deletions(-) delete mode 100644 web/src/main/java/org/springframework/security/http/MediaTypes.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java index c7bb26f91f..bcd2c1b19d 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,6 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -99,7 +98,6 @@ public final class DefaultOAuth2TokenRequestHeadersConverter DefaultOAuth2TokenRequestHeadersConverter withCharsetUtf8() { DefaultOAuth2TokenRequestHeadersConverter converter = new DefaultOAuth2TokenRequestHeadersConverter<>(); - converter.accept = List.of(MediaTypes.APPLICATION_JSON_UTF8); converter.contentType = APPLICATION_FORM_URLENCODED_UTF8; return converter; } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java index e285c61ff6..57cdd38b18 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -119,7 +118,7 @@ public class DefaultAuthorizationCodeTokenResponseClientTests { Instant expiresAtAfter = Instant.now().plusSeconds(3600); RecordedRequest recordedRequest = this.server.takeRequest(); assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.APPLICATION_JSON_UTF8_VALUE); + 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(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java index d175bcf22b..96c6de171c 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -122,7 +121,7 @@ public class DefaultClientCredentialsTokenResponseClientTests { Instant expiresAtAfter = Instant.now().plusSeconds(3600); RecordedRequest recordedRequest = this.server.takeRequest(); assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.APPLICATION_JSON_UTF8_VALUE); + 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(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java index a00a9b0090..d2a95f6418 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -116,7 +115,7 @@ public class DefaultJwtBearerTokenResponseClientTests { Instant expiresAtAfter = Instant.now().plusSeconds(3600); RecordedRequest recordedRequest = this.server.takeRequest(); assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.APPLICATION_JSON_UTF8_VALUE); + 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(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverterTests.java index 2ba7888ecd..f264679796 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,9 +35,6 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class DefaultOAuth2TokenRequestHeadersConverterTests { - private static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON, - StandardCharsets.UTF_8); - private static final MediaType APPLICATION_FORM_URLENCODED_UTF8 = new MediaType( MediaType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8); @@ -92,7 +89,7 @@ public class DefaultOAuth2TokenRequestHeadersConverterTests { // @formatter:on OAuth2ClientCredentialsGrantRequest grantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration); HttpHeaders defaultHeaders = this.converter.convert(grantRequest); - assertThat(defaultHeaders.getAccept()).containsExactly(APPLICATION_JSON_UTF8); + assertThat(defaultHeaders.getAccept()).containsExactly(MediaType.APPLICATION_JSON); assertThat(defaultHeaders.getContentType()).isEqualTo(APPLICATION_FORM_URLENCODED_UTF8); assertThat(defaultHeaders.getFirst(HttpHeaders.AUTHORIZATION)) .isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0JTNE"); @@ -110,7 +107,7 @@ public class DefaultOAuth2TokenRequestHeadersConverterTests { // @formatter:on OAuth2ClientCredentialsGrantRequest grantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration); HttpHeaders defaultHeaders = this.converter.convert(grantRequest); - assertThat(defaultHeaders.getAccept()).containsExactly(APPLICATION_JSON_UTF8); + assertThat(defaultHeaders.getAccept()).containsExactly(MediaType.APPLICATION_JSON); assertThat(defaultHeaders.getContentType()).isEqualTo(APPLICATION_FORM_URLENCODED_UTF8); assertThat(defaultHeaders.getFirst(HttpHeaders.AUTHORIZATION)) .isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0PQ=="); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java index a33b234e64..62ea7dc2c3 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -116,7 +115,7 @@ public class DefaultPasswordTokenResponseClientTests { Instant expiresAtAfter = Instant.now().plusSeconds(3600); RecordedRequest recordedRequest = this.server.takeRequest(); assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.APPLICATION_JSON_UTF8_VALUE); + 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(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java index 61c4f2d2de..c1ee06fcd3 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -118,7 +117,7 @@ public class DefaultRefreshTokenTokenResponseClientTests { Instant expiresAtAfter = Instant.now().plusSeconds(3600); RecordedRequest recordedRequest = this.server.takeRequest(); assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); - assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaTypes.APPLICATION_JSON_UTF8_VALUE); + 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"); assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); 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 index 9624c6465c..4d0afc9163 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -149,8 +149,7 @@ public class DefaultTokenExchangeTokenResponseClientTests { 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.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(); @@ -189,8 +188,7 @@ public class DefaultTokenExchangeTokenResponseClientTests { 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.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(); @@ -229,8 +227,7 @@ public class DefaultTokenExchangeTokenResponseClientTests { 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.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(); @@ -271,8 +268,7 @@ public class DefaultTokenExchangeTokenResponseClientTests { 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.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(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java index 88d06285b2..d228e2bd8a 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -135,7 +134,7 @@ public class JwtBearerGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaType.valueOf(MediaTypes.APPLICATION_JSON_UTF8_VALUE)); + assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java index ac824a681d..a90201938b 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -139,7 +138,7 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0JTNE"); @@ -178,7 +177,7 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isNull(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java index af25ccb9f1..91d9e36c1c 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -124,7 +123,7 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); @@ -153,7 +152,7 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); String urlEncodedClientCredential = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters, diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java index 5a6a8811e8..a9de0ceba7 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -119,7 +118,7 @@ public class OAuth2PasswordGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java index fee0e2b02c..959b92fbb0 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; -import org.springframework.security.http.MediaTypes; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -131,7 +130,7 @@ public class OAuth2RefreshTokenGrantRequestEntityConverterTests { assertThat(requestEntity.getUrl().toASCIIString()) .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaTypes.APPLICATION_JSON_UTF8); + assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); 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 index 8a77a66dfb..38c2c29e6f 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -161,8 +161,7 @@ public class TokenExchangeGrantRequestEntityConverterTests { 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.getAccept()).contains(MediaType.APPLICATION_JSON); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); @@ -197,8 +196,7 @@ public class TokenExchangeGrantRequestEntityConverterTests { 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.getAccept()).contains(MediaType.APPLICATION_JSON); assertThat(headers.getContentType()) .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isNull(); diff --git a/web/src/main/java/org/springframework/security/http/MediaTypes.java b/web/src/main/java/org/springframework/security/http/MediaTypes.java deleted file mode 100644 index 96b6c2d989..0000000000 --- a/web/src/main/java/org/springframework/security/http/MediaTypes.java +++ /dev/null @@ -1,39 +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.http; - -import java.nio.charset.StandardCharsets; - -import org.springframework.http.MediaType; - -/** - * This is a placeholder to allow an incremental update to Spring Framework 7.0. - * - * @deprecated For removal - */ -@Deprecated(since = "7.0.0", forRemoval = true) -public final class MediaTypes { - - public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON, - StandardCharsets.UTF_8); - - public static final String APPLICATION_JSON_UTF8_VALUE = APPLICATION_JSON_UTF8.toString(); - - private MediaTypes() { - } - -} From 48eb2430127d074459fe9f149de51e0288f10885 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Wed, 7 May 2025 23:41:30 +0700 Subject: [PATCH 032/149] Update javadoc Signed-off-by: Tran Ngoc Nhan --- .../endpoint/DefaultOAuth2TokenRequestHeadersConverter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java index bcd2c1b19d..40419e1143 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java @@ -91,8 +91,8 @@ public final class DefaultOAuth2TokenRequestHeadersConverter Date: Fri, 25 Apr 2025 02:26:48 +0900 Subject: [PATCH 033/149] Add logging to CsrfTokenRequestHandler implementations Add trace-level logging to show the logical path of CSRF token processing - Log token source (header or parameter) in resolveCsrfTokenValue - Log request attribute names in handle methods - Log failures in XorCsrfTokenRequestAttributeHandler (especially Base64 decoding) - Add similar logging to XorServerCsrfTokenRequestAttributeHandler Improves debugging capabilities without changing functionality. Closes gh-13626 Signed-off-by: yybmion --- .../security/web/csrf/CsrfFilter.java | 3 ++ .../CsrfTokenRequestAttributeHandler.java | 11 ++++++- .../web/csrf/CsrfTokenRequestHandler.java | 20 +++++++++--- .../CsrfTokenRequestHandlerLoggerHolder.java | 32 +++++++++++++++++++ .../XorCsrfTokenRequestAttributeHandler.java | 15 ++++++++- ...erverCsrfTokenRequestAttributeHandler.java | 9 +++++- .../csrf/ServerCsrfTokenRequestHandler.java | 25 +++++++++++++-- ...erCsrfTokenRequestHandlerLoggerHolder.java | 32 +++++++++++++++++++ ...erverCsrfTokenRequestAttributeHandler.java | 12 ++++++- 9 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandlerLoggerHolder.java create mode 100644 web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandlerLoggerHolder.java diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java index 2164675c74..f2e52554d0 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java @@ -119,6 +119,9 @@ public final class CsrfFilter extends OncePerRequestFilter { } CsrfToken csrfToken = deferredCsrfToken.get(); String actualToken = this.requestHandler.resolveCsrfTokenValue(request, csrfToken); + if (actualToken != null && this.logger.isTraceEnabled()) { + this.logger.trace(LogMessage.format("Found a CSRF token in the request")); + } if (!equalsConstantTime(csrfToken.getToken(), actualToken)) { boolean missingToken = deferredCsrfToken.isGenerated(); this.logger diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestAttributeHandler.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestAttributeHandler.java index a0950fa44b..7e9a124c4d 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestAttributeHandler.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestAttributeHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,10 @@ import java.util.function.Supplier; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.log.LogMessage; import org.springframework.util.Assert; /** @@ -29,10 +32,13 @@ import org.springframework.util.Assert; * value as either a header or parameter value of the request. * * @author Steve Riesenberg + * @author Yoobin Yoon * @since 5.8 */ public class CsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler { + private static final Log logger = LogFactory.getLog(CsrfTokenRequestAttributeHandler.class); + private String csrfRequestAttributeName = "_csrf"; /** @@ -60,6 +66,9 @@ public class CsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName : csrfToken.getParameterName(); request.setAttribute(csrfAttrName, csrfToken); + + logger.trace(LogMessage.format("Wrote a CSRF token to the following request attributes: [%s, %s]", csrfAttrName, + CsrfToken.class.getName())); } @SuppressWarnings("serial") diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandler.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandler.java index cbdf7d76dc..320c556faf 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandler.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.util.function.Supplier; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.springframework.core.log.LogMessage; import org.springframework.util.Assert; /** @@ -30,6 +31,7 @@ import org.springframework.util.Assert; * available to the application through request attributes. * * @author Steve Riesenberg + * @author Yoobin Yoon * @since 5.8 * @see CsrfTokenRequestAttributeHandler */ @@ -49,10 +51,20 @@ public interface CsrfTokenRequestHandler extends CsrfTokenRequestResolver { Assert.notNull(request, "request cannot be null"); Assert.notNull(csrfToken, "csrfToken cannot be null"); String actualToken = request.getHeader(csrfToken.getHeaderName()); - if (actualToken == null) { - actualToken = request.getParameter(csrfToken.getParameterName()); + if (actualToken != null) { + return actualToken; } - return actualToken; + CsrfTokenRequestHandlerLoggerHolder.logger.trace( + LogMessage.format("Did not find a CSRF token in the [%s] request header", csrfToken.getHeaderName())); + + actualToken = request.getParameter(csrfToken.getParameterName()); + if (actualToken != null) { + return actualToken; + } + CsrfTokenRequestHandlerLoggerHolder.logger.trace(LogMessage + .format("Did not find a CSRF token in the [%s] request parameter", csrfToken.getParameterName())); + + return null; } } diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandlerLoggerHolder.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandlerLoggerHolder.java new file mode 100644 index 0000000000..d90f675b56 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandlerLoggerHolder.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.csrf; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Utility class for holding the logger for {@link CsrfTokenRequestHandler} + */ +final class CsrfTokenRequestHandlerLoggerHolder { + + static final Log logger = LogFactory.getLog(CsrfTokenRequestHandler.class); + + private CsrfTokenRequestHandlerLoggerHolder() { + } + +} diff --git a/web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestAttributeHandler.java b/web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestAttributeHandler.java index 9416d233eb..a59920e83f 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestAttributeHandler.java +++ b/web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestAttributeHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,10 @@ import java.util.function.Supplier; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.log.LogMessage; import org.springframework.security.crypto.codec.Utf8; import org.springframework.util.Assert; @@ -32,10 +35,13 @@ import org.springframework.util.Assert; * value from the masked value as either a header or parameter value of the request. * * @author Steve Riesenberg + * @author Yoobin Yoon * @since 5.8 */ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestAttributeHandler { + private static final Log logger = LogFactory.getLog(XorCsrfTokenRequestAttributeHandler.class); + private SecureRandom secureRandom = new SecureRandom(); /** @@ -70,6 +76,9 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA @Override public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { String actualToken = super.resolveCsrfTokenValue(request, csrfToken); + if (actualToken == null) { + return null; + } return getTokenValue(actualToken, csrfToken.getToken()); } @@ -79,12 +88,16 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA actualBytes = Base64.getUrlDecoder().decode(actualToken); } catch (Exception ex) { + logger.trace(LogMessage.format("Not returning the CSRF token since it's not Base64-encoded"), ex); return null; } byte[] tokenBytes = Utf8.encode(token); int tokenSize = tokenBytes.length; if (actualBytes.length != tokenSize * 2) { + logger.trace(LogMessage.format( + "Not returning the CSRF token since its Base64-decoded length (%d) is not equal to (%d)", + actualBytes.length, tokenSize * 2)); return null; } diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestAttributeHandler.java b/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestAttributeHandler.java index 888239d522..949c54ab06 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestAttributeHandler.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestAttributeHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,11 @@ package org.springframework.security.web.server.csrf; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; +import org.springframework.core.log.LogMessage; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.codec.multipart.FormFieldPart; @@ -31,10 +34,13 @@ import org.springframework.web.server.ServerWebExchange; * resolving the token value as either a form data value or header of the request. * * @author Steve Riesenberg + * @author Yoobin Yoon * @since 5.8 */ public class ServerCsrfTokenRequestAttributeHandler implements ServerCsrfTokenRequestHandler { + private static final Log logger = LogFactory.getLog(ServerCsrfTokenRequestAttributeHandler.class); + private boolean isTokenFromMultipartDataEnabled; @Override @@ -42,6 +48,7 @@ public class ServerCsrfTokenRequestAttributeHandler implements ServerCsrfTokenRe Assert.notNull(exchange, "exchange cannot be null"); Assert.notNull(csrfToken, "csrfToken cannot be null"); exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken); + logger.trace(LogMessage.format("Wrote a CSRF token to the [%s] exchange attribute", CsrfToken.class.getName())); } @Override diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandler.java b/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandler.java index a0f0221662..341e2cb7b0 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandler.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandler.java @@ -18,6 +18,7 @@ package org.springframework.security.web.server.csrf; import reactor.core.publisher.Mono; +import org.springframework.core.log.LogMessage; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; @@ -46,9 +47,27 @@ public interface ServerCsrfTokenRequestHandler extends ServerCsrfTokenRequestRes default Mono resolveCsrfTokenValue(ServerWebExchange exchange, CsrfToken csrfToken) { Assert.notNull(exchange, "exchange cannot be null"); Assert.notNull(csrfToken, "csrfToken cannot be null"); - return exchange.getFormData() - .flatMap((data) -> Mono.justOrEmpty(data.getFirst(csrfToken.getParameterName()))) - .switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(csrfToken.getHeaderName()))); + + String headerName = csrfToken.getHeaderName(); + String parameterName = csrfToken.getParameterName(); + + return exchange.getFormData().flatMap((data) -> { + String token = data.getFirst(parameterName); + if (token != null) { + return Mono.just(token); + } + ServerCsrfTokenRequestHandlerLoggerHolder.logger + .trace(LogMessage.format("Did not find a CSRF token in the [%s] request parameter", parameterName)); + return Mono.empty(); + }).switchIfEmpty(Mono.defer(() -> { + String token = exchange.getRequest().getHeaders().getFirst(headerName); + if (token != null) { + return Mono.just(token); + } + ServerCsrfTokenRequestHandlerLoggerHolder.logger + .trace(LogMessage.format("Did not find a CSRF token in the [%s] request header", headerName)); + return Mono.empty(); + })); } } diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandlerLoggerHolder.java b/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandlerLoggerHolder.java new file mode 100644 index 0000000000..b6cddf1e4d --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandlerLoggerHolder.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.server.csrf; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Utility class for holding the logger for {@link ServerCsrfTokenRequestHandler} + */ +final class ServerCsrfTokenRequestHandlerLoggerHolder { + + static final Log logger = LogFactory.getLog(ServerCsrfTokenRequestHandler.class); + + private ServerCsrfTokenRequestHandlerLoggerHolder() { + } + +} diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/XorServerCsrfTokenRequestAttributeHandler.java b/web/src/main/java/org/springframework/security/web/server/csrf/XorServerCsrfTokenRequestAttributeHandler.java index 32e3642351..184a8ee8a8 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/XorServerCsrfTokenRequestAttributeHandler.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/XorServerCsrfTokenRequestAttributeHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,11 @@ package org.springframework.security.web.server.csrf; import java.security.SecureRandom; import java.util.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; +import org.springframework.core.log.LogMessage; import org.springframework.security.crypto.codec.Utf8; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; @@ -32,10 +35,13 @@ import org.springframework.web.server.ServerWebExchange; * masked value as either a form data value or header of the request. * * @author Steve Riesenberg + * @author Yoobin Yoon * @since 5.8 */ public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfTokenRequestAttributeHandler { + private static final Log logger = LogFactory.getLog(XorServerCsrfTokenRequestAttributeHandler.class); + private SecureRandom secureRandom = new SecureRandom(); /** @@ -72,12 +78,16 @@ public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfT actualBytes = Base64.getUrlDecoder().decode(actualToken); } catch (Exception ex) { + logger.trace(LogMessage.format("Not returning the CSRF token since it's not Base64-encoded"), ex); return null; } byte[] tokenBytes = Utf8.encode(token); int tokenSize = tokenBytes.length; if (actualBytes.length != tokenSize * 2) { + logger.trace(LogMessage.format( + "Not returning the CSRF token since its Base64-decoded length (%d) is not equal to (%d)", + actualBytes.length, tokenSize * 2)); return null; } From 99330bfc60704252ae3487df380dd4ef441f8bd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 03:47:41 +0000 Subject: [PATCH 034/149] Bump org-apache-maven-resolver from 1.9.22 to 1.9.23 Bumps `org-apache-maven-resolver` from 1.9.22 to 1.9.23. Updates `org.apache.maven.resolver:maven-resolver-connector-basic` from 1.9.22 to 1.9.23 - [Release notes](https://github.com/apache/maven-resolver/releases) - [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.22...maven-resolver-1.9.23) Updates `org.apache.maven.resolver:maven-resolver-impl` from 1.9.22 to 1.9.23 - [Release notes](https://github.com/apache/maven-resolver/releases) - [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.22...maven-resolver-1.9.23) Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.22 to 1.9.23 --- updated-dependencies: - dependency-name: org.apache.maven.resolver:maven-resolver-connector-basic dependency-version: 1.9.23 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.maven.resolver:maven-resolver-impl dependency-version: 1.9.23 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.maven.resolver:maven-resolver-transport-http dependency-version: 1.9.23 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9a4ed86714..40d54aebf0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ io-spring-javaformat = "0.0.43" io-spring-nohttp = "0.0.11" jakarta-websocket = "2.2.0" org-apache-directory-server = "1.5.5" -org-apache-maven-resolver = "1.9.22" +org-apache-maven-resolver = "1.9.23" org-aspectj = "1.9.24" org-bouncycastle = "1.80" org-eclipse-jetty = "11.0.25" From f1e3f2a8d341594343d5db351d912f6f57f20ea0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 03:21:54 +0000 Subject: [PATCH 035/149] Bump com.webauthn4j:webauthn4j-core Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.29.1.RELEASE to 0.29.2.RELEASE. - [Release notes](https://github.com/webauthn4j/webauthn4j/releases) - [Changelog](https://github.com/webauthn4j/webauthn4j/blob/master/github-release-notes-generator.yml) - [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.29.1.RELEASE...0.29.2.RELEASE) --- updated-dependencies: - dependency-name: com.webauthn4j:webauthn4j-core dependency-version: 0.29.2.RELEASE dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 40d54aebf0..aa184b3acc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -108,7 +108,7 @@ org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-inf org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8.0.1969" org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1" -webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.29.1.RELEASE' +webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.29.2.RELEASE' [plugins] From 8b925dc4fca6a483e8273c863aa24fe260f91786 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 03:22:06 +0000 Subject: [PATCH 036/149] Bump io.micrometer:micrometer-observation from 1.14.6 to 1.14.7 Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.6 to 1.14.7. - [Release notes](https://github.com/micrometer-metrics/micrometer/releases) - [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.6...v1.14.7) --- updated-dependencies: - dependency-name: io.micrometer:micrometer-observation dependency-version: 1.14.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa184b3acc..dd5a8ae205 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11" com-unboundid-unboundid-ldapsdk7 = "com.unboundid:unboundid-ldapsdk:7.0.1" commons-collections = "commons-collections:commons-collections:3.2.2" io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3" -io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.6" +io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.7" io-mockk = "io.mockk:mockk:1.14.2" io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-M2" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } From 462e38c0e36c57f073e68ba4bbfae1ff0fa5606e Mon Sep 17 00:00:00 2001 From: David Kowis Date: Thu, 8 May 2025 12:35:59 -0500 Subject: [PATCH 037/149] Fix DPoP jkt claim to be JWK SHA-256 thumbprint Just used the nimbus JOSE library to do it, because it already has a compliant implementation. Closes gh-17080 Signed-off-by: David Kowis --- .../DPoPAuthenticationProvider.java | 11 ++++------ .../DPoPAuthenticationProviderTests.java | 20 +++++++------------ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java index 32d7b09fa1..aa98e6c2f5 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java @@ -210,25 +210,22 @@ public final class DPoPAuthenticationProvider implements AuthenticationProvider return OAuth2TokenValidatorResult.failure(error); } - PublicKey publicKey = null; + JWK jwk = null; @SuppressWarnings("unchecked") Map jwkJson = (Map) jwt.getHeaders().get("jwk"); try { - JWK jwk = JWK.parse(jwkJson); - if (jwk instanceof AsymmetricJWK) { - publicKey = ((AsymmetricJWK) jwk).toPublicKey(); - } + jwk = JWK.parse(jwkJson); } catch (Exception ignored) { } - if (publicKey == null) { + if (jwk == null) { OAuth2Error error = createOAuth2Error("jwk header is missing or invalid."); return OAuth2TokenValidatorResult.failure(error); } String jwkThumbprint; try { - jwkThumbprint = computeSHA256(publicKey); + jwkThumbprint = jwk.computeThumbprint().toString(); } catch (Exception ex) { OAuth2Error error = createOAuth2Error("Failed to compute SHA-256 Thumbprint for jwk."); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java index 08aec38900..bc2d83101e 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; @@ -218,8 +219,8 @@ public class DPoPAuthenticationProviderTests { @Test public void authenticateWhenJktDoesNotMatchThenThrowOAuth2AuthenticationException() throws Exception { - // Use different client public key - Jwt accessToken = generateAccessToken(TestKeys.DEFAULT_EC_KEY_PAIR.getPublic()); + // Use different jwk to make it not match + Jwt accessToken = generateAccessToken(TestJwks.DEFAULT_EC_JWK); JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(accessToken); given(this.tokenAuthenticationManager.authenticate(any())).willReturn(jwtAuthenticationToken); @@ -285,14 +286,14 @@ public class DPoPAuthenticationProviderTests { } private Jwt generateAccessToken() { - return generateAccessToken(TestKeys.DEFAULT_PUBLIC_KEY); + return generateAccessToken(TestJwks.DEFAULT_RSA_JWK); } - private Jwt generateAccessToken(PublicKey clientPublicKey) { + private Jwt generateAccessToken(JWK clientJwk) { Map jktClaim = null; - if (clientPublicKey != null) { + if (clientJwk != null) { try { - String sha256Thumbprint = computeSHA256(clientPublicKey); + String sha256Thumbprint = clientJwk.computeThumbprint().toString(); jktClaim = new HashMap<>(); jktClaim.put("jkt", sha256Thumbprint); } @@ -321,11 +322,4 @@ public class DPoPAuthenticationProviderTests { byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8)); return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); } - - private static String computeSHA256(PublicKey publicKey) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(publicKey.getEncoded()); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - } From 44303d2c80d01eef30ea4219296ed4cd09ec52e7 Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Tue, 13 May 2025 08:15:00 -0400 Subject: [PATCH 038/149] Polish gh-17080 --- .../DPoPAuthenticationConfigurerTests.java | 32 +++++++------------ .../DPoPAuthenticationProvider.java | 8 ----- .../DPoPAuthenticationProviderTests.java | 5 ++- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/DPoPAuthenticationConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/DPoPAuthenticationConfigurerTests.java index d908607a1f..6ffab052e9 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/DPoPAuthenticationConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/DPoPAuthenticationConfigurerTests.java @@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -import java.security.PublicKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; @@ -33,6 +32,7 @@ import java.util.Set; import java.util.UUID; import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.JWKSource; @@ -89,6 +89,8 @@ public class DPoPAuthenticationConfigurerTests { private static final ECPrivateKey CLIENT_EC_PRIVATE_KEY = (ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate(); + private static final ECKey CLIENT_EC_KEY = TestJwks.jwk(CLIENT_EC_PUBLIC_KEY, CLIENT_EC_PRIVATE_KEY).build(); + private static NimbusJwtEncoder providerJwtEncoder; private static NimbusJwtEncoder clientJwtEncoder; @@ -104,9 +106,8 @@ public class DPoPAuthenticationConfigurerTests { JWKSource providerJwkSource = (jwkSelector, securityContext) -> jwkSelector .select(new JWKSet(providerRsaKey)); providerJwtEncoder = new NimbusJwtEncoder(providerJwkSource); - ECKey clientEcKey = TestJwks.jwk(CLIENT_EC_PUBLIC_KEY, CLIENT_EC_PRIVATE_KEY).build(); JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector - .select(new JWKSet(clientEcKey)); + .select(new JWKSet(CLIENT_EC_KEY)); clientJwtEncoder = new NimbusJwtEncoder(clientJwkSource); } @@ -114,7 +115,7 @@ public class DPoPAuthenticationConfigurerTests { public void requestWhenDPoPAndBearerAuthenticationThenUnauthorized() throws Exception { this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); Set scope = Collections.singleton("resource1.read"); - String accessToken = generateAccessToken(scope, CLIENT_EC_PUBLIC_KEY); + String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); // @formatter:off this.mvc.perform(get("/resource1") @@ -131,7 +132,7 @@ public class DPoPAuthenticationConfigurerTests { public void requestWhenDPoPAccessTokenMalformedThenUnauthorized() throws Exception { this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); Set scope = Collections.singleton("resource1.read"); - String accessToken = generateAccessToken(scope, CLIENT_EC_PUBLIC_KEY); + String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); // @formatter:off this.mvc.perform(get("/resource1") @@ -147,7 +148,7 @@ public class DPoPAuthenticationConfigurerTests { public void requestWhenMultipleDPoPProofsThenUnauthorized() throws Exception { this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); Set scope = Collections.singleton("resource1.read"); - String accessToken = generateAccessToken(scope, CLIENT_EC_PUBLIC_KEY); + String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); // @formatter:off this.mvc.perform(get("/resource1") @@ -164,7 +165,7 @@ public class DPoPAuthenticationConfigurerTests { public void requestWhenDPoPAuthenticationValidThenAccessed() throws Exception { this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); Set scope = Collections.singleton("resource1.read"); - String accessToken = generateAccessToken(scope, CLIENT_EC_PUBLIC_KEY); + String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); // @formatter:off this.mvc.perform(get("/resource1") @@ -175,11 +176,11 @@ public class DPoPAuthenticationConfigurerTests { // @formatter:on } - private static String generateAccessToken(Set scope, PublicKey clientPublicKey) { + private static String generateAccessToken(Set scope, JWK jwk) { Map jktClaim = null; - if (clientPublicKey != null) { + if (jwk != null) { try { - String sha256Thumbprint = computeSHA256(clientPublicKey); + String sha256Thumbprint = jwk.toPublicJWK().computeThumbprint().toString(); jktClaim = new HashMap<>(); jktClaim.put("jkt", sha256Thumbprint); } @@ -207,10 +208,7 @@ public class DPoPAuthenticationConfigurerTests { private static String generateDPoPProof(String method, String resourceUri, String accessToken) throws Exception { // @formatter:off - Map publicJwk = TestJwks.jwk(CLIENT_EC_PUBLIC_KEY, CLIENT_EC_PRIVATE_KEY) - .build() - .toPublicJWK() - .toJSONObject(); + Map publicJwk = CLIENT_EC_KEY.toPublicJWK().toJSONObject(); JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) .type("dpop+jwt") .jwk(publicJwk) @@ -233,12 +231,6 @@ public class DPoPAuthenticationConfigurerTests { return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); } - private static String computeSHA256(PublicKey publicKey) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(publicKey.getEncoded()); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - @Configuration @EnableWebSecurity @EnableWebMvc diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java index aa98e6c2f5..0b904254f8 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java @@ -18,13 +18,11 @@ package org.springframework.security.oauth2.server.resource.authentication; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -import java.security.PublicKey; import java.time.Instant; import java.util.Base64; import java.util.Map; import java.util.function.Function; -import com.nimbusds.jose.jwk.AsymmetricJWK; import com.nimbusds.jose.jwk.JWK; import org.springframework.security.authentication.AuthenticationManager; @@ -243,12 +241,6 @@ public final class DPoPAuthenticationProvider implements AuthenticationProvider return new OAuth2Error(OAuth2ErrorCodes.INVALID_DPOP_PROOF, reason, null); } - private static String computeSHA256(PublicKey publicKey) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(publicKey.getEncoded()); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - } private static final class OAuth2AccessTokenClaims implements OAuth2Token, ClaimAccessor { diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java index bc2d83101e..faa84d8cb4 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java @@ -18,7 +18,6 @@ package org.springframework.security.oauth2.server.resource.authentication; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -import java.security.PublicKey; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Base64; @@ -37,7 +36,6 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.TestKeys; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.Jwt; @@ -293,7 +291,7 @@ public class DPoPAuthenticationProviderTests { Map jktClaim = null; if (clientJwk != null) { try { - String sha256Thumbprint = clientJwk.computeThumbprint().toString(); + String sha256Thumbprint = clientJwk.toPublicJWK().computeThumbprint().toString(); jktClaim = new HashMap<>(); jktClaim.put("jkt", sha256Thumbprint); } @@ -322,4 +320,5 @@ public class DPoPAuthenticationProviderTests { byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8)); return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); } + } From 86550fb84b19590ef4cfb656efd44a18662c01db Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Sat, 10 May 2025 02:17:47 +0700 Subject: [PATCH 039/149] Cleanup code Signed-off-by: Tran Ngoc Nhan --- .../WebSessionServerRequestCache.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java b/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java index 2c1f8aac10..396d93fb25 100644 --- a/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,7 +64,8 @@ public class WebSessionServerRequestCache implements ServerRequestCache { /** * Sets the matcher to determine if the request should be saved. The default is to * match on any GET request. - * @param saveRequestMatcher + * @param saveRequestMatcher the {@link ServerWebExchangeMatcher} that determines if + * the request should be saved */ public void setSaveRequestMatcher(ServerWebExchangeMatcher saveRequestMatcher) { Assert.notNull(saveRequestMatcher, "saveRequestMatcher cannot be null"); @@ -96,7 +97,7 @@ public class WebSessionServerRequestCache implements ServerRequestCache { public Mono removeMatchingRequest(ServerWebExchange exchange) { MultiValueMap queryParams = exchange.getRequest().getQueryParams(); if (this.matchingRequestParameterName != null && !queryParams.containsKey(this.matchingRequestParameterName)) { - this.logger.trace( + logger.trace( "matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided"); return Mono.empty(); } @@ -165,17 +166,4 @@ public class WebSessionServerRequestCache implements ServerRequestCache { return new AndServerWebExchangeMatcher(get, notFavicon, html); } - private static String createQueryString(String queryString, String matchingRequestParameterName) { - if (matchingRequestParameterName == null) { - return queryString; - } - if (queryString == null || queryString.length() == 0) { - return matchingRequestParameterName; - } - if (queryString.endsWith("&")) { - return queryString + matchingRequestParameterName; - } - return queryString + "&" + matchingRequestParameterName; - } - } From a5111713095e4d13a4a7dda4441ebc71120aa751 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Thu, 8 May 2025 18:02:43 +0700 Subject: [PATCH 040/149] Add test and update javadoc for CommonOAuth2Provider Signed-off-by: Tran Ngoc Nhan --- .../oauth2/client/CommonOAuth2Provider.java | 5 +++-- .../client/CommonOAuth2ProviderTests.java | 20 ++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java b/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java index 96d0e51eac..c2e84336f6 100644 --- a/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java +++ b/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.security.config.oauth2.client; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder; @@ -27,7 +28,7 @@ import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; * Common OAuth2 Providers that can be used to create * {@link org.springframework.security.oauth2.client.registration.ClientRegistration.Builder * builders} pre-configured with sensible defaults for the - * {@link HttpSecurity#oauth2Login()} flow. + * {@link HttpSecurity#oauth2Login(Customizer)} flow. * * @author Phillip Webb * @since 5.0 diff --git a/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java b/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java index 81ab751a60..0a3bea1389 100644 --- a/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java +++ b/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -115,6 +115,24 @@ public class CommonOAuth2ProviderTests { assertThat(registration.getRegistrationId()).isEqualTo("123"); } + @Test + public void getBuilderWhenXShouldHaveXSettings() { + ClientRegistration registration = build(CommonOAuth2Provider.X); + ProviderDetails providerDetails = registration.getProviderDetails(); + assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://x.com/i/oauth2/authorize"); + assertThat(providerDetails.getTokenUri()).isEqualTo("https://api.x.com/2/oauth2/token"); + assertThat(providerDetails.getUserInfoEndpoint().getUri()).isEqualTo("https://api.x.com/2/users/me"); + assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("username"); + assertThat(providerDetails.getJwkSetUri()).isNull(); + assertThat(registration.getClientAuthenticationMethod()) + .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST); + assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); + assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); + assertThat(registration.getScopes()).containsOnly("users.read", "tweet.read"); + assertThat(registration.getClientName()).isEqualTo("X"); + assertThat(registration.getRegistrationId()).isEqualTo("123"); + } + private ClientRegistration build(CommonOAuth2Provider provider) { return builder(provider).build(); } From c22091d8be857793fb6e59e66f5bce17a40d140d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 May 2025 03:50:59 +0000 Subject: [PATCH 041/149] Bump io.projectreactor:reactor-bom from 2025.0.0-M2 to 2025.0.0-M3 Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2025.0.0-M2 to 2025.0.0-M3. - [Release notes](https://github.com/reactor/reactor/releases) - [Commits](https://github.com/reactor/reactor/compare/2025.0.0-M2...2025.0.0-M3) --- updated-dependencies: - dependency-name: io.projectreactor:reactor-bom dependency-version: 2025.0.0-M3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd5a8ae205..47cd26e7c3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,7 +31,7 @@ commons-collections = "commons-collections:commons-collections:3.2.2" io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3" io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.7" io-mockk = "io.mockk:mockk:1.14.2" -io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-M2" +io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-M3" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" } io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" } From 3b492a96288d91c71798eb4f536fcc0d625787c4 Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Mon, 12 May 2025 22:43:34 +0300 Subject: [PATCH 042/149] remove 32-byte minimum keyLength restriction in `Base64StringKeyGenerator` (#17012) Signed-off-by: Andrey Litvitski --- .../security/crypto/keygen/Base64StringKeyGenerator.java | 7 ++++--- .../crypto/keygen/Base64StringKeyGeneratorTests.java | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crypto/src/main/java/org/springframework/security/crypto/keygen/Base64StringKeyGenerator.java b/crypto/src/main/java/org/springframework/security/crypto/keygen/Base64StringKeyGenerator.java index 9b28d68a72..40e89d10fb 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/keygen/Base64StringKeyGenerator.java +++ b/crypto/src/main/java/org/springframework/security/crypto/keygen/Base64StringKeyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import java.util.Base64; * * @author Joe Grandja * @author Rob Winch + * @author Andrey Litvitski * @since 5.0 */ public class Base64StringKeyGenerator implements StringKeyGenerator { @@ -67,8 +68,8 @@ public class Base64StringKeyGenerator implements StringKeyGenerator { if (encoder == null) { throw new IllegalArgumentException("encode cannot be null"); } - if (keyLength < DEFAULT_KEY_LENGTH) { - throw new IllegalArgumentException("keyLength must be greater than or equal to " + DEFAULT_KEY_LENGTH); + if (keyLength <= 0) { + throw new IllegalArgumentException("keyLength must be greater than 0"); } this.encoder = encoder; this.keyGenerator = KeyGenerators.secureRandom(keyLength); diff --git a/crypto/src/test/java/org/springframework/security/crypto/keygen/Base64StringKeyGeneratorTests.java b/crypto/src/test/java/org/springframework/security/crypto/keygen/Base64StringKeyGeneratorTests.java index 4590b67683..a781513f68 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/keygen/Base64StringKeyGeneratorTests.java +++ b/crypto/src/test/java/org/springframework/security/crypto/keygen/Base64StringKeyGeneratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,13 +25,14 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException /** * @author Rob Winch + * @author Andrey Litvitski * @since 5.0 */ public class Base64StringKeyGeneratorTests { @Test - public void constructorIntWhenLessThan32ThenIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> new Base64StringKeyGenerator(31)); + public void constructorIntWhenEqual0ThenIllegalArgumentException() { + assertThatIllegalArgumentException().isThrownBy(() -> new Base64StringKeyGenerator(0)); } @Test From 817938fa49f567bc6f38b63087378b392ef95fc6 Mon Sep 17 00:00:00 2001 From: huhdy32 Date: Sat, 10 May 2025 22:51:12 +0900 Subject: [PATCH 043/149] Add NullReturningMethodAuthorizationDeniedHandler This implementation of MethodAuthorizationDeniedHandler returns null when authorization is denied. Closes gh-16705 Signed-off-by: huhdy32 --- ...rningMethodAuthorizationDeniedHandler.java | 49 ++++++++++++++++ ...MethodAuthorizationDeniedHandlerTests.java | 58 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 core/src/main/java/org/springframework/security/authorization/method/NullReturningMethodAuthorizationDeniedHandler.java create mode 100644 core/src/test/java/org/springframework/security/authorization/method/NullReturningMethodAuthorizationDeniedHandlerTests.java diff --git a/core/src/main/java/org/springframework/security/authorization/method/NullReturningMethodAuthorizationDeniedHandler.java b/core/src/main/java/org/springframework/security/authorization/method/NullReturningMethodAuthorizationDeniedHandler.java new file mode 100644 index 0000000000..f81a7cb50b --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/NullReturningMethodAuthorizationDeniedHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.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 return {@code null}. + * + * @author Heejong Yoon + * @since 6.5.0 + */ +public final class NullReturningMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { + + @Override + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + if (authorizationResult instanceof AuthorizationDeniedException exception) { + throw exception; + } + return null; + } + + @Override + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, + AuthorizationResult authorizationResult) { + if (authorizationResult instanceof AuthorizationDeniedException exception) { + throw exception; + } + return null; + } + +} diff --git a/core/src/test/java/org/springframework/security/authorization/method/NullReturningMethodAuthorizationDeniedHandlerTests.java b/core/src/test/java/org/springframework/security/authorization/method/NullReturningMethodAuthorizationDeniedHandlerTests.java new file mode 100644 index 0000000000..0064280d1b --- /dev/null +++ b/core/src/test/java/org/springframework/security/authorization/method/NullReturningMethodAuthorizationDeniedHandlerTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authorization.method; + +import org.junit.jupiter.api.Test; + +import org.springframework.security.authorization.AuthorizationDeniedException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link NullReturningMethodAuthorizationDeniedHandler}. + * + * @author Heejong Yoon + */ +class NullReturningMethodAuthorizationDeniedHandlerTests { + + @Test + void handleNullReturningMethod() { + assertThat(new NullReturningMethodAuthorizationDeniedHandler().handleDeniedInvocation(null, null)).isNull(); + } + + @Test + void handleNullReturningMethodWithException() { + assertThatExceptionOfType(AuthorizationDeniedException.class) + .isThrownBy(() -> new NullReturningMethodAuthorizationDeniedHandler().handleDeniedInvocation(null, + new AuthorizationDeniedException("test"))); + } + + @Test + void handleNullReturningMethodWithInvocationResult() { + assertThat(new NullReturningMethodAuthorizationDeniedHandler().handleDeniedInvocationResult(null, null)) + .isNull(); + } + + @Test + void handleNullReturningMethodWithInvocationResultWithException() { + assertThatExceptionOfType(AuthorizationDeniedException.class) + .isThrownBy(() -> new NullReturningMethodAuthorizationDeniedHandler().handleDeniedInvocationResult(null, + new AuthorizationDeniedException("test"))); + } + +} From 0722c2dc41f3a14db51d3be6629c6278aad703a0 Mon Sep 17 00:00:00 2001 From: Junhyeok Lee Date: Thu, 8 May 2025 01:41:22 +0900 Subject: [PATCH 044/149] Implement UserDetailsPasswordService in JdbcUserDetailsManager Signed-off-by: Junhyeok Lee --- .../provisioning/JdbcUserDetailsManager.java | 30 ++++++++++++++++++- .../JdbcUserDetailsManagerTests.java | 17 +++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java index 5856580cce..951e3d25ab 100644 --- a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java +++ b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java @@ -46,6 +46,7 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserCache; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsPasswordService; import org.springframework.security.core.userdetails.cache.NullUserCache; import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl; import org.springframework.util.Assert; @@ -65,7 +66,8 @@ import org.springframework.util.Assert; * @author Luke Taylor * @since 2.0 */ -public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsManager, GroupManager { +public class JdbcUserDetailsManager extends JdbcDaoImpl + implements UserDetailsManager, GroupManager, UserDetailsPasswordService { public static final String DEF_CREATE_USER_SQL = "insert into users (username, password, enabled) values (?,?,?)"; @@ -162,6 +164,8 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa private RowMapper grantedAuthorityMapper = this::mapToGrantedAuthority; + private boolean enableUpdatePassword = false; + public JdbcUserDetailsManager() { } @@ -591,6 +595,20 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa this.userCache = userCache; } + /** + * Sets whether the {@link #updatePassword(UserDetails, String)} method should + * actually update the password. + *

+ * Defaults to {@code false} to prevent accidental password updates that might produce + * passwords that are too large for the current database schema. Users must explicitly + * set this to {@code true} to enable password updates. + * @param enableUpdatePassword {@code true} to enable password updates, {@code false} + * otherwise. + */ + public void setEnableUpdatePassword(boolean enableUpdatePassword) { + this.enableUpdatePassword = enableUpdatePassword; + } + private void validateUserDetails(UserDetails user) { Assert.hasText(user.getUsername(), "Username may not be empty or null"); validateAuthorities(user.getAuthorities()); @@ -604,4 +622,14 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa } } + @Override + public UserDetails updatePassword(UserDetails user, String newPassword) { + if (this.enableUpdatePassword) { + UserDetails updated = User.withUserDetails(user).password(newPassword).build(); + updateUser(updated); + return updated; + } + return user; + } + } diff --git a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java index 5c8ed72b18..db49411c25 100644 --- a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java +++ b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java @@ -410,6 +410,23 @@ public class JdbcUserDetailsManagerTests { verify(mockMapper).mapRow(any(), anyInt()); } + @Test + void updatePasswordWhenDisabledReturnOriginalUser() { + insertJoe(); + this.manager.updatePassword(joe, "new"); + UserDetails newJoe = this.manager.loadUserByUsername("joe"); + assertThat(newJoe.getPassword()).isEqualTo("password"); + } + + @Test + void updatePasswordWhenEnabledShouldUpdatePassword() { + insertJoe(); + this.manager.setEnableUpdatePassword(true); + this.manager.updatePassword(joe, "new"); + UserDetails newJoe = this.manager.loadUserByUsername("joe"); + assertThat(newJoe.getPassword()).isEqualTo("new"); + } + private Authentication authenticateJoe() { UsernamePasswordAuthenticationToken auth = UsernamePasswordAuthenticationToken.authenticated("joe", "password", joe.getAuthorities()); From e30dc42d1e4a969552a205b3bbbeae5f5a78390a Mon Sep 17 00:00:00 2001 From: Junhyeok Lee Date: Sat, 10 May 2025 00:14:44 +0900 Subject: [PATCH 045/149] Update JdbcUserDetailsManager Javadoc and author Signed-off-by: Junhyeok Lee --- .../security/provisioning/JdbcUserDetailsManager.java | 5 +++++ .../security/provisioning/JdbcUserDetailsManagerTests.java | 1 + 2 files changed, 6 insertions(+) diff --git a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java index 951e3d25ab..5bd8aecdf3 100644 --- a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java +++ b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java @@ -64,6 +64,7 @@ import org.springframework.util.Assert; * using this implementation for managing your users. * * @author Luke Taylor + * @author Junhyeok Lee * @since 2.0 */ public class JdbcUserDetailsManager extends JdbcDaoImpl @@ -622,6 +623,10 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl } } + /** + * Conditionally updates password based on the setting from + * {@link #setEnableUpdatePassword(boolean)}. {@inheritDoc} + */ @Override public UserDetails updatePassword(UserDetails user, String newPassword) { if (this.enableUpdatePassword) { diff --git a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java index db49411c25..3f7962e6f4 100644 --- a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java +++ b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java @@ -60,6 +60,7 @@ import static org.mockito.BDDMockito.verify; * * @author Luke Taylor * @author dae won + * @author Junhyeok Lee */ public class JdbcUserDetailsManagerTests { From 4fb3dca9530ee4f6a85b33ff1711753dbcf72c97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 03:27:53 +0000 Subject: [PATCH 046/149] Bump org.hibernate.orm:hibernate-core from 7.0.0.CR1 to 7.0.0.CR2 Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.0.0.CR1 to 7.0.0.CR2. - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/main/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.0.0.CR1...7.0.0.CR2) --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 7.0.0.CR2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 47cd26e7c3..d99094e4e9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,7 +71,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", 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:7.0.0.CR1" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.0.0.CR2" org-hsqldb = "org.hsqldb:hsqldb:2.7.4" 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.25" From e772025646276876e12f080c6cb53b02818ee71c Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Mon, 19 May 2025 11:18:23 -0600 Subject: [PATCH 047/149] Update What's New in 6.5 --- docs/modules/ROOT/pages/whats-new.adoc | 44 ++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index a75c999dd4..3614b65f8f 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -3,11 +3,7 @@ Spring Security 6.5 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. - -== New Features - -* Support for automatic context-propagation with Micrometer (https://github.com/spring-projects/spring-security/issues/16665[gh-16665]) -* OAuth 2.0 Demonstrating Proof of Possession (DPoP) (https://github.com/spring-projects/spring-security/pull/16574[gh-16574]) +Given that this is the last minor release in the 6.x generation, please consider reading the https://docs.spring.io/spring-security/reference/6.5-SNAPSHOT/migration-7/index.html[Prepare for the 7.0 Migration Guide]. == Breaking Changes @@ -16,10 +12,46 @@ Below are the highlights of the release, or you can view https://github.com/spri The `security.security.reached.filter.section` key name was corrected to `spring.security.reached.filter.section`. Note that this may affect reports that operate on this key name. -== OAuth +== New Features +* https://github.com/spring-projects/spring-security/issues/16665[gh-16665] - Support for automatic context-propagation with Micrometer +* https://github.com/spring-projects/spring-security/pull/16574[gh-16574] - OAuth 2.0 Demonstrating Proof of Possession (DPoP) + +== Core + +* https://github.com/spring-projects/spring-security/issues/16444[gh-16444] - Add `Authentication` request to ``AuthenticationException``s +* https://github.com/spring-projects/spring-security/issues/16291[gh-16291] - Improve error messaging for impossible authorization configurations + +== Messaging + +* https://github.com/spring-projects/spring-security/pull/16635[gh-16635] - Add `PathPatternMessageMatcher` +* https://github.com/spring-projects/spring-security/issues/16766[gh-16766] - Add `matcher` support to `MessageMatcher` + +== OAuth 2.0 + +* https://github.com/spring-projects/spring-security/issues/16380[gh-16380] - Pick up `OAuth2AuthorizationRequestResolver` as a bean * https://github.com/spring-projects/spring-security/pull/16386[gh-16386] - Enable PKCE for confidential clients using `ClientRegistration.clientSettings.requireProofKey=true` for xref:servlet/oauth2/client/core.adoc#oauth2Client-client-registration-requireProofKey[servlet] and xref:reactive/oauth2/client/core.adoc#oauth2Client-client-registration-requireProofKey[reactive] applications * https://github.com/spring-projects/spring-security/issues/16913[gh-16913] - Prepare OAuth2 Client deprecations for removal in Spring Security 7 +* https://github.com/spring-projects/spring-security/pull/16574[gh-16574] - Support https://datatracker.ietf.org/doc/html/rfc9449[RFC 9499]: Dynamic Proof of Possession (DPoP) +* https://github.com/spring-projects/spring-security/issues/13185[gh-13185] - OAuth 2.0 Access Token JWT Profile Support (RFC 9068) - https://docs.spring.io/spring-security/reference/6.5-SNAPSHOT/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-validation-rfc9068[(docs)] +* https://github.com/spring-projects/spring-security/pull/16682[gh-16682] - Add `JwtAudienceValidator` +* https://github.com/spring-projects/spring-security/issues/16672[gh-16672] - Add `JwtTypeValidator` + +== SAML 2.0 + +* https://github.com/spring-projects/spring-security/issues/16915[gh-16915] - Simplify support for Response Validation +* https://github.com/spring-projects/spring-security/issues/15578[gh-15578] - Simplify support for Assertion Validation, including support for a custom set of validators +* https://github.com/spring-projects/spring-security/issues/12136[gh-12136] - Simplify support for Response Authentication Conversion, including support for principals not in `` +* https://github.com/spring-projects/spring-security/issues/14793[gh-14793] - Add RelayState-based `` repository + +== Web + +* https://github.com/spring-projects/spring-security/pull/16502[gh-16502] - Add `HttpStatusAccessDeniedHandler` +* https://github.com/spring-projects/spring-security/issues/16059[gh-16059] Add support for `ModelAndView` and +* `ResponseEntity` to `@AuthorizeReturnObject` +* https://github.com/spring-projects/spring-security/issues/16429[gh-16429] - Replace `MvcRequestMatcher` and `AntPathRequestMatcher` with `PathPatternRequestMatcher` +* https://github.com/spring-projects/spring-security/issues/16793[gh-16793] - Add support for `AuthenticationConverter` to `AbstractAuthenticationProcessingFilter` +* https://github.com/spring-projects/spring-security/issues/16678[gh-16678] - Simplify redirect-to-HTTPS support == WebAuthn From b3340536dc645ac5083975a8bddcddb31e97e271 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 03:48:17 +0000 Subject: [PATCH 048/149] Bump io-spring-javaformat from 0.0.43 to 0.0.45 Bumps `io-spring-javaformat` from 0.0.43 to 0.0.45. Updates `io.spring.javaformat:spring-javaformat-checkstyle` from 0.0.43 to 0.0.45 - [Release notes](https://github.com/spring-io/spring-javaformat/releases) - [Commits](https://github.com/spring-io/spring-javaformat/compare/v0.0.43...v0.0.45) Updates `io.spring.javaformat:spring-javaformat-gradle-plugin` from 0.0.43 to 0.0.45 - [Release notes](https://github.com/spring-io/spring-javaformat/releases) - [Commits](https://github.com/spring-io/spring-javaformat/compare/v0.0.43...v0.0.45) --- updated-dependencies: - dependency-name: io.spring.javaformat:spring-javaformat-checkstyle dependency-version: 0.0.45 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.spring.javaformat:spring-javaformat-gradle-plugin dependency-version: 0.0.45 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d99094e4e9..3b86a9aa10 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] com-squareup-okhttp3 = "3.14.9" io-rsocket = "1.1.5" -io-spring-javaformat = "0.0.43" +io-spring-javaformat = "0.0.45" io-spring-nohttp = "0.0.11" jakarta-websocket = "2.2.0" org-apache-directory-server = "1.5.5" From c46f263483fb48372246680252fc211f3c04241a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 03:47:59 +0000 Subject: [PATCH 049/149] Bump org.hibernate.orm:hibernate-core from 7.0.0.CR2 to 7.0.0.Final Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.0.0.CR2 to 7.0.0.Final. - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/main/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.0.0.CR2...7.0.0) --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 7.0.0.Final dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3b86a9aa10..6a16532177 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,7 +71,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", 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:7.0.0.CR2" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.0.0.Final" org-hsqldb = "org.hsqldb:hsqldb:2.7.4" 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.25" From 47338f7e567560b57d073e41ae771f8654e24294 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 22 May 2025 12:29:05 -0600 Subject: [PATCH 050/149] Remove Conflict Markers --- gradle.properties | 9 --------- 1 file changed, 9 deletions(-) diff --git a/gradle.properties b/gradle.properties index 2402e4c544..19a07278b1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,17 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # -<<<<<<< HEAD springBootVersion=4.0.0-SNAPSHOT version=7.0.0-SNAPSHOT -======= -springBootVersion=3.3.3 -<<<<<<< HEAD -version=6.5.1-SNAPSHOT -======= -version=6.4.7-SNAPSHOT ->>>>>>> origin/6.4.x ->>>>>>> 6.5.x samplesBranch=main org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true From ff22866c6dc35cc511aea977a6684bd99ea8c17d Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Thu, 22 May 2025 15:43:38 -0500 Subject: [PATCH 051/149] RepositoryConventionPlugin supports arbitrary repositories --- .../gradle/convention/RepositoryConventionPlugin.groovy | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy index 56fcc71e9c..266f3a5b7a 100644 --- a/buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy @@ -80,6 +80,11 @@ class RepositoryConventionPlugin implements Plugin { } url = 'https://repo.spring.io/release/' } + forceMavenRepositories.findAll { it.startsWith('https://') || it.startsWith('file://') }.each { mavenUrl -> + maven { + url mavenUrl + } + } } } From 64d3397a9c373b6f1c3b28e9e56714fdb2ec1e1e Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Thu, 22 May 2025 15:44:26 -0500 Subject: [PATCH 052/149] Add netty's SNAPSHOT repository to snapshot build --- .github/workflows/continuous-integration-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index f8dd0d5584..11ca8b043f 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -39,7 +39,7 @@ jobs: toolchain: 17 with: java-version: ${{ matrix.java-version }} - test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace + test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace secrets: inherit deploy-artifacts: name: Deploy Artifacts From 50f8ad55a88136c7fd42b71f0673637ef0607bb2 Mon Sep 17 00:00:00 2001 From: universe Date: Thu, 8 May 2025 11:05:38 +0800 Subject: [PATCH 053/149] Remove Redundant Punctation in JavaDoc Signed-off-by: universe --- .../authentication/UsernamePasswordAuthenticationFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.java index 9a9fd4a6dd..6984d0c6a4 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.java @@ -140,7 +140,7 @@ public class UsernamePasswordAuthenticationFilter extends AbstractAuthentication /** * Sets the parameter name which will be used to obtain the password from the login - * request.. + * request. * @param passwordParameter the parameter name. Defaults to "password". */ public void setPasswordParameter(String passwordParameter) { From 88369cd25287fbc54b77f2fcf2d42020c6a7f6e5 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Thu, 22 May 2025 10:55:51 +0700 Subject: [PATCH 054/149] Polish Signed-off-by: Tran Ngoc Nhan --- .../security/provisioning/JdbcUserDetailsManager.java | 2 ++ .../provisioning/JdbcUserDetailsManagerTests.java | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java index 5bd8aecdf3..c521af1d91 100644 --- a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java +++ b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java @@ -605,6 +605,7 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl * set this to {@code true} to enable password updates. * @param enableUpdatePassword {@code true} to enable password updates, {@code false} * otherwise. + * @since 7.0 */ public void setEnableUpdatePassword(boolean enableUpdatePassword) { this.enableUpdatePassword = enableUpdatePassword; @@ -626,6 +627,7 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl /** * Conditionally updates password based on the setting from * {@link #setEnableUpdatePassword(boolean)}. {@inheritDoc} + * @since 7.0 */ @Override public UserDetails updatePassword(UserDetails user, String newPassword) { diff --git a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java index 3f7962e6f4..adb03ee9e8 100644 --- a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java +++ b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java @@ -285,10 +285,9 @@ public class JdbcUserDetailsManagerTests { } @Test - @SuppressWarnings("unchecked") public void createGroupInsertsCorrectData() { this.manager.createGroup("TEST_GROUP", AuthorityUtils.createAuthorityList("ROLE_X", "ROLE_Y")); - List roles = this.template.queryForList("select ga.authority from groups g, group_authorities ga " + List roles = this.template.queryForList("select ga.authority from groups g, group_authorities ga " + "where ga.group_id = g.id " + "and g.group_name = 'TEST_GROUP'"); assertThat(roles).hasSize(2); } @@ -367,7 +366,7 @@ public class JdbcUserDetailsManagerTests { // SEC-2166 @Test - public void createNewAuthenticationUsesNullPasswordToKeepPassordsSave() { + public void createNewAuthenticationUsesNullPasswordToKeepPasswordSave() { insertJoe(); UsernamePasswordAuthenticationToken currentAuth = UsernamePasswordAuthenticationToken.authenticated("joe", null, AuthorityUtils.createAuthorityList("ROLE_USER")); @@ -443,9 +442,9 @@ public class JdbcUserDetailsManagerTests { this.cache.putUserInCache(joe); } - private class MockUserCache implements UserCache { + private static class MockUserCache implements UserCache { - private Map cache = new HashMap<>(); + private final Map cache = new HashMap<>(); @Override public UserDetails getUserFromCache(String username) { From 8e2067bb3ead20e1ff3bb00def1adc0966249e71 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Thu, 22 May 2025 10:56:14 +0700 Subject: [PATCH 055/149] Remove deprecated `MemberCategory#DECLARED_FIELDS` Issue gh-16889 Signed-off-by: Tran Ngoc Nhan --- .../aot/hint/AuthorizeReturnObjectHintsRegistrar.java | 4 ++-- .../security/aot/hint/CoreSecurityRuntimeHints.java | 4 ++-- .../security/aot/hint/CoreSecurityRuntimeHintsTests.java | 6 +++--- .../security/test/aot/hint/WebTestUtilsRuntimeHints.java | 8 ++++---- .../test/aot/hint/WebTestUtilsRuntimeHintsTests.java | 8 ++++---- .../security/web/aot/hint/WebMvcSecurityRuntimeHints.java | 4 ++-- .../web/aot/hint/WebMvcSecurityRuntimeHintsTests.java | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/org/springframework/security/aot/hint/AuthorizeReturnObjectHintsRegistrar.java b/core/src/main/java/org/springframework/security/aot/hint/AuthorizeReturnObjectHintsRegistrar.java index 0c430160b7..fb1bf28483 100644 --- a/core/src/main/java/org/springframework/security/aot/hint/AuthorizeReturnObjectHintsRegistrar.java +++ b/core/src/main/java/org/springframework/security/aot/hint/AuthorizeReturnObjectHintsRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -122,7 +122,7 @@ public final class AuthorizeReturnObjectHintsRegistrar implements SecurityHintsR .registerType(clazz, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS) .registerType(proxied, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS); } } diff --git a/core/src/main/java/org/springframework/security/aot/hint/CoreSecurityRuntimeHints.java b/core/src/main/java/org/springframework/security/aot/hint/CoreSecurityRuntimeHints.java index 833b6d114a..2ac11b0f87 100644 --- a/core/src/main/java/org/springframework/security/aot/hint/CoreSecurityRuntimeHints.java +++ b/core/src/main/java/org/springframework/security/aot/hint/CoreSecurityRuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,7 +79,7 @@ class CoreSecurityRuntimeHints implements RuntimeHintsRegistrar { .registerTypes( List.of(TypeReference.of(SecurityExpressionOperations.class), TypeReference.of(SecurityExpressionRoot.class)), - (builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS, + (builder) -> builder.withMembers(MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS)); } diff --git a/core/src/test/java/org/springframework/security/aot/hint/CoreSecurityRuntimeHintsTests.java b/core/src/test/java/org/springframework/security/aot/hint/CoreSecurityRuntimeHintsTests.java index 7c4eb4615e..f7e0956887 100644 --- a/core/src/test/java/org/springframework/security/aot/hint/CoreSecurityRuntimeHintsTests.java +++ b/core/src/test/java/org/springframework/security/aot/hint/CoreSecurityRuntimeHintsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,7 +81,7 @@ class CoreSecurityRuntimeHintsTests { void securityExpressionOperationsHasHints() { assertThat(RuntimeHintsPredicates.reflection() .onType(SecurityExpressionOperations.class) - .withMemberCategories(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS)) + .withMemberCategories(MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS)) .accepts(this.hints); } @@ -89,7 +89,7 @@ class CoreSecurityRuntimeHintsTests { void securityExpressionRootHasHints() { assertThat(RuntimeHintsPredicates.reflection() .onType(SecurityExpressionRoot.class) - .withMemberCategories(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS)) + .withMemberCategories(MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS)) .accepts(this.hints); } diff --git a/test/src/main/java/org/springframework/security/test/aot/hint/WebTestUtilsRuntimeHints.java b/test/src/main/java/org/springframework/security/test/aot/hint/WebTestUtilsRuntimeHints.java index 9b098dd863..bb89f19f3d 100644 --- a/test/src/main/java/org/springframework/security/test/aot/hint/WebTestUtilsRuntimeHints.java +++ b/test/src/main/java/org/springframework/security/test/aot/hint/WebTestUtilsRuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,12 +58,12 @@ class WebTestUtilsRuntimeHints implements RuntimeHintsRegistrar { } private void registerCsrfTokenRepositoryHints(RuntimeHints hints) { - hints.reflection().registerType(CsrfFilter.class, MemberCategory.DECLARED_FIELDS); + hints.reflection().registerType(CsrfFilter.class, MemberCategory.ACCESS_DECLARED_FIELDS); } private void registerSecurityContextRepositoryHints(RuntimeHints hints) { - hints.reflection().registerType(SecurityContextPersistenceFilter.class, MemberCategory.DECLARED_FIELDS); - hints.reflection().registerType(SecurityContextHolderFilter.class, MemberCategory.DECLARED_FIELDS); + hints.reflection().registerType(SecurityContextPersistenceFilter.class, MemberCategory.ACCESS_DECLARED_FIELDS); + hints.reflection().registerType(SecurityContextHolderFilter.class, MemberCategory.ACCESS_DECLARED_FIELDS); } } diff --git a/test/src/test/java/org/springframework/security/test/aot/hint/WebTestUtilsRuntimeHintsTests.java b/test/src/test/java/org/springframework/security/test/aot/hint/WebTestUtilsRuntimeHintsTests.java index 6fb662dca3..672e3ed8f4 100644 --- a/test/src/test/java/org/springframework/security/test/aot/hint/WebTestUtilsRuntimeHintsTests.java +++ b/test/src/test/java/org/springframework/security/test/aot/hint/WebTestUtilsRuntimeHintsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,21 +72,21 @@ class WebTestUtilsRuntimeHintsTests { void csrfFilterHasHints() { assertThat(RuntimeHintsPredicates.reflection() .onType(CsrfFilter.class) - .withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(this.hints); + .withMemberCategories(MemberCategory.ACCESS_DECLARED_FIELDS)).accepts(this.hints); } @Test void securityContextPersistenceFilterHasHints() { assertThat(RuntimeHintsPredicates.reflection() .onType(SecurityContextPersistenceFilter.class) - .withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(this.hints); + .withMemberCategories(MemberCategory.ACCESS_DECLARED_FIELDS)).accepts(this.hints); } @Test void securityContextHolderFilterHasHints() { assertThat(RuntimeHintsPredicates.reflection() .onType(SecurityContextHolderFilter.class) - .withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(this.hints); + .withMemberCategories(MemberCategory.ACCESS_DECLARED_FIELDS)).accepts(this.hints); } } diff --git a/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHints.java b/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHints.java index 86df3618f2..347b7edc8a 100644 --- a/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHints.java +++ b/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ class WebMvcSecurityRuntimeHints implements RuntimeHintsRegistrar { public void registerHints(RuntimeHints hints, ClassLoader classLoader) { hints.reflection() .registerType(WebSecurityExpressionRoot.class, (builder) -> builder - .withMembers(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS)); + .withMembers(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS)); hints.reflection() .registerType( TypeReference diff --git a/web/src/test/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHintsTests.java b/web/src/test/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHintsTests.java index 180d10db48..5e0a9fcd4f 100644 --- a/web/src/test/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHintsTests.java +++ b/web/src/test/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHintsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,7 +50,7 @@ class WebMvcSecurityRuntimeHintsTests { void webSecurityExpressionRootHasHints() { assertThat(RuntimeHintsPredicates.reflection() .onType(WebSecurityExpressionRoot.class) - .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS)) + .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS)) .accepts(this.hints); } From 9ba5c7b2ce98283a77d67d970bbfa4d2032fde34 Mon Sep 17 00:00:00 2001 From: John Niang Date: Thu, 8 May 2025 11:44:47 +0800 Subject: [PATCH 056/149] Add SwitchUserGrantedAuthority to Web Jackson Module Closes gh-17041 Signed-off-by: John Niang --- .../SwitchUserGrantedAuthorityMixIn.java | 3 +- .../web/jackson2/WebJackson2Module.java | 13 ++++-- .../SwitchUserGrantedAuthorityMixInTests.java | 46 +++++++++++++++---- 3 files changed, 46 insertions(+), 16 deletions(-) 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 index 4fff7ffdc4..f61b8a05bf 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixIn.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixIn.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.springframework.security.web.authentication.switchuser.SwitchUserGran * * @author Markus Heiden * @since 6.3 + * @see WebJackson2Module * @see WebServletJackson2Module * @see org.springframework.security.jackson2.SecurityJackson2Modules */ diff --git a/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java b/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java index 87daedcc40..b8cee4fee0 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,14 +21,16 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.security.jackson2.SecurityJackson2Modules; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority; import org.springframework.security.web.csrf.DefaultCsrfToken; /** * Jackson module for spring-security-web. This module register - * {@link DefaultCsrfTokenMixin} and {@link PreAuthenticatedAuthenticationTokenMixin}. 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. + * {@link DefaultCsrfTokenMixin}, {@link PreAuthenticatedAuthenticationTokenMixin} and + * {@link SwitchUserGrantedAuthorityMixIn}. 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();
@@ -53,6 +55,7 @@ public class WebJackson2Module extends SimpleModule {
 		context.setMixInAnnotations(DefaultCsrfToken.class, DefaultCsrfTokenMixin.class);
 		context.setMixInAnnotations(PreAuthenticatedAuthenticationToken.class,
 				PreAuthenticatedAuthenticationTokenMixin.class);
+		context.setMixInAnnotations(SwitchUserGrantedAuthority.class, SwitchUserGrantedAuthorityMixIn.class);
 	}
 
 }
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
index 703811658c..70bba02513 100644
--- a/web/src/test/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixInTests.java
+++ b/web/src/test/java/org/springframework/security/web/jackson2/SwitchUserGrantedAuthorityMixInTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,14 +16,20 @@
 
 package org.springframework.security.web.jackson2;
 
+import java.util.stream.Stream;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 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.CoreJackson2Module;
+import org.springframework.security.jackson2.SecurityJackson2Modules;
 import org.springframework.security.jackson2.SimpleGrantedAuthorityMixinTests;
 import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority;
 
@@ -33,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
  * @author Markus Heiden
  * @since 6.3
  */
-public class SwitchUserGrantedAuthorityMixInTests extends AbstractMixinTests {
+public class SwitchUserGrantedAuthorityMixInTests {
 
 	// language=JSON
 	private static final String SWITCH_JSON = """
@@ -53,22 +59,42 @@ public class SwitchUserGrantedAuthorityMixInTests extends AbstractMixinTests {
 
 	private Authentication source;
 
+	static Stream mappers() {
+		ObjectMapper securityJackson2ModulesMapper = new ObjectMapper();
+		ClassLoader classLoader = SwitchUserGrantedAuthorityMixInTests.class.getClassLoader();
+		securityJackson2ModulesMapper.registerModules(SecurityJackson2Modules.getModules(classLoader));
+
+		ObjectMapper webJackson2ModuleMapper = new ObjectMapper();
+		webJackson2ModuleMapper.registerModule(new CoreJackson2Module());
+		webJackson2ModuleMapper.registerModule(new WebJackson2Module());
+
+		ObjectMapper webServletJackson2ModuleMapper = new ObjectMapper();
+		webServletJackson2ModuleMapper.registerModule(new CoreJackson2Module());
+		webServletJackson2ModuleMapper.registerModule(new WebServletJackson2Module());
+
+		return Stream.of(Arguments.of(securityJackson2ModulesMapper), Arguments.of(webJackson2ModuleMapper),
+				Arguments.of(webServletJackson2ModuleMapper));
+	}
+
 	@BeforeEach
 	public void setUp() {
 		this.source = new UsernamePasswordAuthenticationToken("principal", "credentials",
 				AuthorityUtils.createAuthorityList("ROLE_USER"));
 	}
 
-	@Test
-	public void serializeWhenPrincipalCredentialsAuthoritiesThenSuccess() throws Exception {
+	@ParameterizedTest
+	@MethodSource("mappers")
+	public void serializeWhenPrincipalCredentialsAuthoritiesThenSuccess(ObjectMapper mapper) throws Exception {
 		SwitchUserGrantedAuthority expected = new SwitchUserGrantedAuthority("switched", this.source);
-		String serializedJson = this.mapper.writeValueAsString(expected);
+		String serializedJson = mapper.writeValueAsString(expected);
 		JSONAssert.assertEquals(SWITCH_JSON, serializedJson, true);
 	}
 
-	@Test
-	public void deserializeWhenSourceIsUsernamePasswordAuthenticationTokenThenSuccess() throws Exception {
-		SwitchUserGrantedAuthority deserialized = this.mapper.readValue(SWITCH_JSON, SwitchUserGrantedAuthority.class);
+	@ParameterizedTest
+	@MethodSource("mappers")
+	public void deserializeWhenSourceIsUsernamePasswordAuthenticationTokenThenSuccess(ObjectMapper mapper)
+			throws Exception {
+		SwitchUserGrantedAuthority deserialized = mapper.readValue(SWITCH_JSON, SwitchUserGrantedAuthority.class);
 		assertThat(deserialized).isNotNull();
 		assertThat(deserialized.getAuthority()).isEqualTo("switched");
 		assertThat(deserialized.getSource()).isEqualTo(this.source);

From f4b8e2421a202575c7654bf2adef5c5b6c651383 Mon Sep 17 00:00:00 2001
From: Max Batischev 
Date: Tue, 13 May 2025 16:00:59 +0300
Subject: [PATCH 057/149] Add Support Credentialless COEP Header

Closes gh-16991

Signed-off-by: Max Batischev 
---
 .../springframework/security/config/spring-security-7.0.rnc | 2 +-
 .../springframework/security/config/spring-security-7.0.xsd | 1 +
 .../writers/CrossOriginEmbedderPolicyHeaderWriter.java      | 6 ++++--
 .../CrossOriginEmbedderPolicyServerHttpHeadersWriter.java   | 6 ++++--
 4 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc
index ec51246b6f..15d15b191b 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc
@@ -1308,7 +1308,7 @@ cross-origin-embedder-policy =
 	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"}?
+	attribute policy {"unsafe-none","require-corp", "credentialless"}?
 
 cross-origin-resource-policy =
 	## Adds support for Cross-Origin-Resource-Policy header
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd
index e254b8488e..34556b5549 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd
@@ -3668,6 +3668,7 @@
             
                
                
+               
             
          
       
diff --git a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java
index d7e2a6dde5..7d9050a8e1 100644
--- a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java
+++ b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -58,7 +58,9 @@ public final class CrossOriginEmbedderPolicyHeaderWriter implements HeaderWriter
 
 		UNSAFE_NONE("unsafe-none"),
 
-		REQUIRE_CORP("require-corp");
+		REQUIRE_CORP("require-corp"),
+
+		CREDENTIALLESS("credentialless");
 
 		private final String policy;
 
diff --git a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java
index 17446845dd..d1d6e72629 100644
--- a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java
+++ b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -61,7 +61,9 @@ public final class CrossOriginEmbedderPolicyServerHttpHeadersWriter implements S
 
 		UNSAFE_NONE("unsafe-none"),
 
-		REQUIRE_CORP("require-corp");
+		REQUIRE_CORP("require-corp"),
+
+		CREDENTIALLESS("credentialless");
 
 		private final String policy;
 

From 8612e952fec7901d3b3d5f371bbf9987c4f8922a Mon Sep 17 00:00:00 2001
From: dae won 
Date: Fri, 25 Apr 2025 20:13:56 +0900
Subject: [PATCH 058/149] Make AuthorizationProxyFactory#proxy Generic

Closes gh-16706

Signed-off-by: dae won 
---
 .../AuthorizationProxyWebConfiguration.java            |  2 +-
 .../AuthorizationProxyConfigurationTests.java          | 10 +++++-----
 .../aot/hint/AuthorizeReturnObjectHintsRegistrar.java  |  2 +-
 .../authorization/AuthorizationProxyFactory.java       |  6 ++++--
 .../method/AuthorizationAdvisorProxyFactory.java       | 10 +++++-----
 .../AuthorizationAdvisorProxyFactoryTests.java         |  2 +-
 6 files changed, 17 insertions(+), 15 deletions(-)

diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java
index 4af062ef96..62faa1a6bc 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java
@@ -51,7 +51,7 @@ class AuthorizationProxyWebConfiguration {
 			if (target instanceof ModelAndView mav) {
 				View view = mav.getView();
 				String viewName = mav.getViewName();
-				Map model = (Map) proxyFactory.proxy(mav.getModel());
+				Map model = proxyFactory.proxy(mav.getModel());
 				ModelAndView proxied = (view != null) ? new ModelAndView(view, model)
 						: new ModelAndView(viewName, model);
 				proxied.setStatus(mav.getStatus());
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
index 254c8b0871..8a66881929 100644
--- 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
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -58,7 +58,7 @@ public class AuthorizationProxyConfigurationTests {
 	@Test
 	public void proxyWhenNotPreAuthorizedThenDenies() {
 		this.spring.register(DefaultsConfig.class).autowire();
-		Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster());
+		Toaster toaster = this.proxyFactory.proxy(new Toaster());
 		assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::makeToast)
 			.withMessage("Access Denied");
 		assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::extractBread)
@@ -69,7 +69,7 @@ public class AuthorizationProxyConfigurationTests {
 	@Test
 	public void proxyWhenPreAuthorizedThenAllows() {
 		this.spring.register(DefaultsConfig.class).autowire();
-		Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster());
+		Toaster toaster = this.proxyFactory.proxy(new Toaster());
 		toaster.makeToast();
 		assertThat(toaster.extractBread()).isEqualTo("yummy");
 	}
@@ -77,7 +77,7 @@ public class AuthorizationProxyConfigurationTests {
 	@Test
 	public void proxyReactiveWhenNotPreAuthorizedThenDenies() {
 		this.spring.register(ReactiveDefaultsConfig.class).autowire();
-		Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster());
+		Toaster toaster = this.proxyFactory.proxy(new Toaster());
 		Authentication user = TestAuthentication.authenticatedUser();
 		StepVerifier
 			.create(toaster.reactiveMakeToast().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user)))
@@ -90,7 +90,7 @@ public class AuthorizationProxyConfigurationTests {
 	@Test
 	public void proxyReactiveWhenPreAuthorizedThenAllows() {
 		this.spring.register(ReactiveDefaultsConfig.class).autowire();
-		Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster());
+		Toaster toaster = this.proxyFactory.proxy(new Toaster());
 		Authentication admin = TestAuthentication.authenticatedAdmin();
 		StepVerifier
 			.create(toaster.reactiveMakeToast().contextWrite(ReactiveSecurityContextHolder.withAuthentication(admin)))
diff --git a/core/src/main/java/org/springframework/security/aot/hint/AuthorizeReturnObjectHintsRegistrar.java b/core/src/main/java/org/springframework/security/aot/hint/AuthorizeReturnObjectHintsRegistrar.java
index fb1bf28483..cf5cd1b17e 100644
--- a/core/src/main/java/org/springframework/security/aot/hint/AuthorizeReturnObjectHintsRegistrar.java
+++ b/core/src/main/java/org/springframework/security/aot/hint/AuthorizeReturnObjectHintsRegistrar.java
@@ -109,7 +109,7 @@ public final class AuthorizeReturnObjectHintsRegistrar implements SecurityHintsR
 	}
 
 	private void registerProxy(RuntimeHints hints, Class clazz) {
-		Class proxied = (Class) this.proxyFactory.proxy(clazz);
+		Class proxied = this.proxyFactory.proxy(clazz);
 		if (proxied == null) {
 			return;
 		}
diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationProxyFactory.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationProxyFactory.java
index c425db9a01..e6e1210e74 100644
--- a/core/src/main/java/org/springframework/security/authorization/AuthorizationProxyFactory.java
+++ b/core/src/main/java/org/springframework/security/authorization/AuthorizationProxyFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ package org.springframework.security.authorization;
  * A factory for wrapping arbitrary objects in authorization-related advice
  *
  * @author Josh Cummings
+ * @author daewon kim
  * @since 6.3
  * @see org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory
  */
@@ -30,11 +31,12 @@ public interface AuthorizationProxyFactory {
 	 *
 	 * 

* Please check the implementation for which kinds of objects it supports. + * @param the type of the object being proxied * @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); + T proxy(T object); } 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 index dd4abb3754..f0f3984cb7 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisorProxyFactory.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisorProxyFactory.java @@ -172,16 +172,16 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx * @return the proxied instance */ @Override - public Object proxy(Object target) { + public T proxy(T target) { if (target == null) { return null; } if (target instanceof AuthorizationProxy proxied) { - return proxied; + return (T) proxied; } Object proxied = this.visitor.visit(this, target); if (proxied != null) { - return proxied; + return (T) proxied; } ProxyFactory factory = new ProxyFactory(target); factory.addAdvisors(this.authorizationProxy); @@ -191,7 +191,7 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx factory.addInterface(AuthorizationProxy.class); factory.setOpaque(true); factory.setProxyTargetClass(!Modifier.isFinal(target.getClass().getModifiers())); - return factory.getProxy(); + return (T) factory.getProxy(); } /** @@ -442,7 +442,7 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx @SuppressWarnings("unchecked") private T proxyCast(AuthorizationProxyFactory proxyFactory, T target) { - return (T) proxyFactory.proxy(target); + return proxyFactory.proxy(target); } private Iterable proxyIterable(AuthorizationProxyFactory proxyFactory, Iterable iterable) { diff --git a/core/src/test/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactoryTests.java b/core/src/test/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactoryTests.java index 93d7ee1520..3babccf147 100644 --- a/core/src/test/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactoryTests.java +++ b/core/src/test/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactoryTests.java @@ -335,7 +335,7 @@ public class AuthorizationAdvisorProxyFactoryTests { @Test public void setTargetVisitorIgnoreValueTypesThenIgnores() { AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults(); - assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> ((Integer) factory.proxy(35)).intValue()); + assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> factory.proxy(35).intValue()); factory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes()); assertThat(factory.proxy(35)).isEqualTo(35); } From fae61b942660b807eb1611af07bdb23122307050 Mon Sep 17 00:00:00 2001 From: Evgeniy Cheban Date: Thu, 8 May 2025 05:58:50 +0300 Subject: [PATCH 059/149] Propagate AccessDeniedException for Authorized Objects Returned from a Controller Closes gh-16058 Signed-off-by: Evgeniy Cheban --- .../AuthorizationProxyWebConfiguration.java | 48 +++- ...ePostMethodSecurityConfigurationTests.java | 228 ++++++++++++++++++ 2 files changed, 275 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java index 62faa1a6bc..85fefe4f57 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java @@ -16,20 +16,30 @@ package org.springframework.security.config.annotation.method.configuration; +import java.util.List; import java.util.Map; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + 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.http.HttpEntity; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; +import org.springframework.security.web.util.ThrowableAnalyzer; +import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; @Configuration -class AuthorizationProxyWebConfiguration { +class AuthorizationProxyWebConfiguration implements WebMvcConfigurer { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @@ -37,6 +47,18 @@ class AuthorizationProxyWebConfiguration { return new WebTargetVisitor(); } + @Override + public void extendHandlerExceptionResolvers(List resolvers) { + for (int i = 0; i < resolvers.size(); i++) { + HandlerExceptionResolver resolver = resolvers.get(i); + if (resolver instanceof DefaultHandlerExceptionResolver) { + resolvers.add(i, new HttpMessageNotWritableAccessDeniedExceptionResolver()); + return; + } + } + resolvers.add(new HttpMessageNotWritableAccessDeniedExceptionResolver()); + } + static class WebTargetVisitor implements AuthorizationAdvisorProxyFactory.TargetVisitor { @Override @@ -62,4 +84,28 @@ class AuthorizationProxyWebConfiguration { } + static class HttpMessageNotWritableAccessDeniedExceptionResolver implements HandlerExceptionResolver { + + final ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer(); + + @Override + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, + Exception ex) { + // Only resolves AccessDeniedException if it occurred during serialization, + // otherwise lets the user-defined handler deal with it. + if (ex instanceof HttpMessageNotWritableException) { + Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex); + Throwable accessDeniedException = this.throwableAnalyzer + .getFirstThrowableOfType(AccessDeniedException.class, causeChain); + if (accessDeniedException != null) { + return new ModelAndView((model, req, res) -> { + throw ex; + }); + } + } + return null; + } + + } + } 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 8eec0f1bce..fe9ccfc77e 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 @@ -33,6 +33,7 @@ import io.micrometer.observation.ObservationHandler; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationTextPublisher; import jakarta.annotation.security.DenyAll; +import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; @@ -42,6 +43,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; import org.springframework.aop.Advisor; +import org.springframework.aop.Pointcut; import org.springframework.aop.config.AopConfigUtils; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.JdkRegexpMethodPointcut; @@ -62,8 +64,11 @@ import org.springframework.context.event.EventListener; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.annotation.BusinessService; @@ -95,6 +100,7 @@ import org.springframework.security.authorization.method.MethodAuthorizationDeni import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.PrePostTemplateDefaults; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.observation.SecurityObservationSettings; import org.springframework.security.config.test.SpringTestContext; @@ -106,13 +112,24 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; +import org.springframework.security.web.util.ThrowableAnalyzer; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -127,6 +144,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link PrePostMethodSecurityConfiguration}. @@ -148,6 +168,9 @@ public class PrePostMethodSecurityConfigurationTests { @Autowired(required = false) BusinessService businessService; + @Autowired(required = false) + MockMvc mvc; + @WithMockUser @Test public void customMethodSecurityPreAuthorizeAdminWhenRoleUserThenAccessDeniedException() { @@ -1181,6 +1204,97 @@ public class PrePostMethodSecurityConfigurationTests { } } + @Test + void getWhenPostAuthorizeAuthenticationNameMatchesThenRespondsWithOk() throws Exception { + this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestWithUser = get("/authorized-person") + .param("name", "rob") + .with(user("rob")); + // @formatter:on + this.mvc.perform(requestWithUser).andExpect(status().isOk()); + } + + @Test + void getWhenPostAuthorizeAuthenticationNameNotMatchThenRespondsWithForbidden() throws Exception { + this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestWithUser = get("/authorized-person") + .param("name", "john") + .with(user("rob")); + // @formatter:on + this.mvc.perform(requestWithUser).andExpect(status().isForbidden()); + } + + @Test + void getWhenPostAuthorizeWithinServiceAuthenticationNameMatchesThenRespondsWithOk() throws Exception { + this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class, BasicService.class).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestWithUser = get("/greetings/authorized-person") + .param("name", "rob") + .with(user("rob")); + // @formatter:on + MvcResult mvcResult = this.mvc.perform(requestWithUser).andExpect(status().isOk()).andReturn(); + assertThat(mvcResult.getResponse().getContentAsString()).isEqualTo("Hello: rob"); + } + + @Test + void getWhenPostAuthorizeWithinServiceAuthenticationNameNotMatchThenCustomHandlerRespondsWithForbidden() + throws Exception { + this.spring + .register(WebMvcMethodSecurityConfig.class, BasicController.class, BasicService.class, + BasicControllerAdvice.class) + .autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestWithUser = get("/greetings/authorized-person") + .param("name", "john") + .with(user("rob")); + // @formatter:on + MvcResult mvcResult = this.mvc.perform(requestWithUser).andExpect(status().isForbidden()).andReturn(); + assertThat(mvcResult.getResponse().getContentAsString()).isEqualTo(""" + {"message":"Access Denied"}\ + """); + } + + @Test + void getWhenPostAuthorizeAuthenticationNameNotMatchThenCustomHandlerRespondsWithForbidden() throws Exception { + this.spring + .register(WebMvcMethodSecurityConfig.class, BasicController.class, BasicService.class, + BasicControllerAdvice.class) + .autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestWithUser = get("/authorized-person") + .param("name", "john") + .with(user("rob")); + // @formatter:on + MvcResult mvcResult = this.mvc.perform(requestWithUser).andExpect(status().isForbidden()).andReturn(); + assertThat(mvcResult.getResponse().getContentAsString()).isEqualTo(""" + {"message":"Could not write JSON: Access Denied"}\ + """); + } + + @Test + void getWhenCustomAdvisorAuthenticationNameMatchesThenRespondsWithOk() throws Exception { + this.spring.register(WebMvcMethodSecurityCustomAdvisorConfig.class, BasicController.class).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestWithUser = get("/authorized-person") + .param("name", "rob") + .with(user("rob")); + // @formatter:on + this.mvc.perform(requestWithUser).andExpect(status().isOk()); + } + + @Test + void getWhenCustomAdvisorAuthenticationNameNotMatchThenRespondsWithForbidden() throws Exception { + this.spring.register(WebMvcMethodSecurityCustomAdvisorConfig.class, BasicController.class).autowire(); + // @formatter:off + MockHttpServletRequestBuilder requestWithUser = get("/authorized-person") + .param("name", "john") + .with(user("rob")); + // @formatter:on + this.mvc.perform(requestWithUser).andExpect(status().isForbidden()); + } + private static Consumer disallowBeanOverriding() { return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false); } @@ -1919,4 +2033,118 @@ public class PrePostMethodSecurityConfigurationTests { } + @EnableWebMvc + @EnableWebSecurity + @EnableMethodSecurity + static class WebMvcMethodSecurityConfig { + + } + + @EnableWebMvc + @EnableWebSecurity + @EnableMethodSecurity + static class WebMvcMethodSecurityCustomAdvisorConfig { + + @Bean + AuthorizationAdvisor customAdvisor(SecurityContextHolderStrategy strategy) { + JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); + pointcut.setPattern(".*AuthorizedPerson.*getName"); + return new AuthorizationAdvisor() { + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + Authentication auth = strategy.getContext().getAuthentication(); + Object result = mi.proceed(); + if (auth.getName().equals(result)) { + return result; + } + throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'"); + } + + @Override + public Pointcut getPointcut() { + return pointcut; + } + + @Override + public Advice getAdvice() { + return this; + } + + @Override + public int getOrder() { + return AuthorizationInterceptorsOrder.POST_FILTER.getOrder() + 1; + } + }; + } + + } + + @RestController + static class BasicController { + + @Autowired(required = false) + BasicService service; + + @GetMapping("/greetings/authorized-person") + String getAuthorizedPersonGreeting(@RequestParam String name) { + AuthorizedPerson authorizedPerson = this.service.getAuthorizedPerson(name); + return "Hello: " + authorizedPerson.getName(); + } + + @AuthorizeReturnObject + @GetMapping(value = "/authorized-person", produces = MediaType.APPLICATION_JSON_VALUE) + AuthorizedPerson getAuthorizedPerson(@RequestParam String name) { + return new AuthorizedPerson(name); + } + + } + + @ControllerAdvice + static class BasicControllerAdvice { + + @ExceptionHandler(AccessDeniedException.class) + ResponseEntity> handleAccessDenied(AccessDeniedException ex) { + Map responseBody = Map.of("message", ex.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(responseBody); + } + + @ExceptionHandler(HttpMessageNotWritableException.class) + ResponseEntity> handleHttpMessageNotWritable(HttpMessageNotWritableException ex) { + ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer(); + Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); + Throwable t = throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); + if (t != null) { + Map responseBody = Map.of("message", ex.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(responseBody); + } + throw ex; + } + + } + + @Service + static class BasicService { + + @AuthorizeReturnObject + AuthorizedPerson getAuthorizedPerson(String name) { + return new AuthorizedPerson(name); + } + + } + + public static class AuthorizedPerson { + + final String name; + + AuthorizedPerson(String name) { + this.name = name; + } + + @PostAuthorize("returnObject == authentication.name") + public String getName() { + return this.name; + } + + } + } From 52394c1f079cc28126c801ae91f640ed37a4b44c Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 23 May 2025 15:00:01 -0600 Subject: [PATCH 060/149] Propagate Any AccessDeniedException Any time a response handler throws an exception, we want to propagate an underlying AccessDeniedException if their is one. Issue gh-16058 --- .../AuthorizationProxyWebConfiguration.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java index 85fefe4f57..be26ed3713 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyWebConfiguration.java @@ -28,7 +28,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Role; import org.springframework.http.HttpEntity; import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; import org.springframework.security.web.util.ThrowableAnalyzer; @@ -52,11 +51,11 @@ class AuthorizationProxyWebConfiguration implements WebMvcConfigurer { for (int i = 0; i < resolvers.size(); i++) { HandlerExceptionResolver resolver = resolvers.get(i); if (resolver instanceof DefaultHandlerExceptionResolver) { - resolvers.add(i, new HttpMessageNotWritableAccessDeniedExceptionResolver()); + resolvers.add(i, new AccessDeniedExceptionResolver()); return; } } - resolvers.add(new HttpMessageNotWritableAccessDeniedExceptionResolver()); + resolvers.add(new AccessDeniedExceptionResolver()); } static class WebTargetVisitor implements AuthorizationAdvisorProxyFactory.TargetVisitor { @@ -84,24 +83,20 @@ class AuthorizationProxyWebConfiguration implements WebMvcConfigurer { } - static class HttpMessageNotWritableAccessDeniedExceptionResolver implements HandlerExceptionResolver { + static class AccessDeniedExceptionResolver implements HandlerExceptionResolver { final ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer(); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { - // Only resolves AccessDeniedException if it occurred during serialization, - // otherwise lets the user-defined handler deal with it. - if (ex instanceof HttpMessageNotWritableException) { - Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex); - Throwable accessDeniedException = this.throwableAnalyzer - .getFirstThrowableOfType(AccessDeniedException.class, causeChain); - if (accessDeniedException != null) { - return new ModelAndView((model, req, res) -> { - throw ex; - }); - } + Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex); + Throwable accessDeniedException = this.throwableAnalyzer + .getFirstThrowableOfType(AccessDeniedException.class, causeChain); + if (accessDeniedException != null) { + return new ModelAndView((model, req, res) -> { + throw ex; + }); } return null; } From 1a4de49977458bdc89e88c1775a2b5f9030f4299 Mon Sep 17 00:00:00 2001 From: Felix Hagemans Date: Fri, 18 Apr 2025 14:18:24 +0200 Subject: [PATCH 061/149] Create CsrfCustomizer for SPA configuration Closes gh-14149 Signed-off-by: Felix Hagemans --- .../web/configurers/CsrfConfigurer.java | 60 ++++++++++++ .../web/configurers/CsrfConfigurerTests.java | 44 +++++++++ .../ROOT/pages/servlet/exploits/csrf.adoc | 96 +------------------ 3 files changed, 109 insertions(+), 91 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java index 55c8f81ab8..833efe4e22 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java @@ -19,9 +19,11 @@ package org.springframework.security.config.annotation.web.configurers; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.function.Supplier; import io.micrometer.observation.ObservationRegistry; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.context.ApplicationContext; import org.springframework.security.access.AccessDeniedException; @@ -34,13 +36,17 @@ import org.springframework.security.web.access.CompositeAccessDeniedHandler; import org.springframework.security.web.access.DelegatingAccessDeniedHandler; import org.springframework.security.web.access.ObservationMarkingAccessDeniedHandler; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfAuthenticationStrategy; import org.springframework.security.web.csrf.CsrfFilter; import org.springframework.security.web.csrf.CsrfLogoutHandler; +import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.CsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; import org.springframework.security.web.csrf.CsrfTokenRequestHandler; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.security.web.csrf.MissingCsrfTokenException; +import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler; import org.springframework.security.web.session.InvalidSessionStrategy; import org.springframework.security.web.util.matcher.AndRequestMatcher; @@ -48,6 +54,7 @@ import org.springframework.security.web.util.matcher.NegatedRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Adds @@ -214,6 +221,21 @@ public final class CsrfConfigurer> return this; } + /** + *

+ * Sensible CSRF defaults when used in combination with a single page application. + * Creates a cookie-based token repository and a custom request handler to resolve the + * actual token value instead of the encoded token. + *

+ * @return the {@link CsrfConfigurer} for further customizations + * @since 7.0 + */ + public CsrfConfigurer spa() { + this.csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); + this.requestHandler = new SpaCsrfTokenRequestHandler(); + return this; + } + @SuppressWarnings("unchecked") @Override public void configure(H http) { @@ -375,4 +397,42 @@ public final class CsrfConfigurer> } + private static class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler { + + private final CsrfTokenRequestAttributeHandler plain = new CsrfTokenRequestAttributeHandler(); + + private final CsrfTokenRequestAttributeHandler xor = new XorCsrfTokenRequestAttributeHandler(); + + SpaCsrfTokenRequestHandler() { + this.xor.setCsrfRequestAttributeName(null); + } + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, Supplier csrfToken) { + /* + * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection + * of the CsrfToken when it is rendered in the response body. + */ + this.xor.handle(request, response, csrfToken); + } + + @Override + public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { + String headerValue = request.getHeader(csrfToken.getHeaderName()); + /* + * If the request contains a request header, use + * CsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies + * when a single-page application includes the header value automatically, + * which was obtained via a cookie containing the raw CsrfToken. + * + * In all other cases (e.g. if the request contains a request parameter), use + * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies + * when a server-side rendered form includes the _csrf request parameter as a + * hidden input. + */ + return (StringUtils.hasText(headerValue) ? this.plain : this.xor).resolveCsrfTokenValue(request, csrfToken); + } + + } + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java index 216579b64d..04a288078f 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java @@ -93,6 +93,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -613,6 +614,37 @@ public class CsrfConfigurerTests { assertThat(cookies).isEmpty(); } + @Test + public void spaConfigForbidden() throws Exception { + this.spring.register(CsrfSpaConfig.class, AllowHttpMethodsFirewallConfig.class, BasicController.class) + .autowire(); + this.mvc.perform(post("/")).andExpect(status().isForbidden()); + } + + @Test + public void spaConfigOk() throws Exception { + this.spring.register(CsrfSpaConfig.class, AllowHttpMethodsFirewallConfig.class, BasicController.class) + .autowire(); + this.mvc.perform(post("/").with(csrf())).andExpect(status().isOk()); + } + + @Test + public void spaConfigDoubleSubmit() throws Exception { + this.spring.register(CsrfSpaConfig.class, AllowHttpMethodsFirewallConfig.class, BasicController.class) + .autowire(); + var token = this.mvc.perform(post("/")) + .andExpect(status().isForbidden()) + .andExpect(cookie().exists("XSRF-TOKEN")) + .andReturn() + .getResponse() + .getCookie("XSRF-TOKEN"); + + this.mvc + .perform(post("/").header("X-XSRF-TOKEN", token.getValue()) + .cookie(new Cookie("XSRF-TOKEN", token.getValue()))) + .andExpect(status().isOk()); + } + @Configuration static class AllowHttpMethodsFirewallConfig { @@ -1006,6 +1038,18 @@ public class CsrfConfigurerTests { } + @Configuration + @EnableWebSecurity + static class CsrfSpaConfig { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf(CsrfConfigurer::spa); + return http.build(); + } + + } + @Configuration @EnableWebSecurity static class HttpBasicCsrfTokenRequestHandlerConfig { diff --git a/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc b/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc index ab4af97dde..f02350dc6d 100644 --- a/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc +++ b/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc @@ -787,48 +787,10 @@ public class SecurityConfig { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // ... - .csrf((csrf) -> csrf - .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // <1> - .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) // <2> - ); + .csrf((csrf) -> csrf.spa()); return http.build(); } } - -final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler { - private final CsrfTokenRequestHandler plain = new CsrfTokenRequestAttributeHandler(); - private final CsrfTokenRequestHandler xor = new XorCsrfTokenRequestAttributeHandler(); - - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, Supplier csrfToken) { - /* - * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of - * the CsrfToken when it is rendered in the response body. - */ - this.xor.handle(request, response, csrfToken); - /* - * Render the token value to a cookie by causing the deferred token to be loaded. - */ - csrfToken.get(); - } - - @Override - public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { - String headerValue = request.getHeader(csrfToken.getHeaderName()); - /* - * If the request contains a request header, use CsrfTokenRequestAttributeHandler - * to resolve the CsrfToken. This applies when a single-page application includes - * the header value automatically, which was obtained via a cookie containing the - * raw CsrfToken. - * - * In all other cases (e.g. if the request contains a request parameter), use - * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies - * when a server-side rendered form includes the _csrf request parameter as a - * hidden input. - */ - return (StringUtils.hasText(headerValue) ? this.plain : this.xor).resolveCsrfTokenValue(request, csrfToken); - } -} ---- Kotlin:: @@ -846,51 +808,12 @@ class SecurityConfig { http { // ... csrf { - csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() // <1> - csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() // <2> + spa() } } return http.build() } } - -class SpaCsrfTokenRequestHandler : CsrfTokenRequestHandler { - private val plain: CsrfTokenRequestHandler = CsrfTokenRequestAttributeHandler() - private val xor: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler() - - override fun handle(request: HttpServletRequest, response: HttpServletResponse, csrfToken: Supplier) { - /* - * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of - * the CsrfToken when it is rendered in the response body. - */ - xor.handle(request, response, csrfToken) - /* - * Render the token value to a cookie by causing the deferred token to be loaded. - */ - csrfToken.get() - } - - override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String? { - val headerValue = request.getHeader(csrfToken.headerName) - /* - * If the request contains a request header, use CsrfTokenRequestAttributeHandler - * to resolve the CsrfToken. This applies when a single-page application includes - * the header value automatically, which was obtained via a cookie containing the - * raw CsrfToken. - */ - return if (StringUtils.hasText(headerValue)) { - plain - } else { - /* - * In all other cases (e.g. if the request contains a request parameter), use - * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies - * when a server-side rendered form includes the _csrf request parameter as a - * hidden input. - */ - xor - }.resolveCsrfTokenValue(request, csrfToken) - } -} ---- XML:: @@ -899,22 +822,13 @@ XML:: ---- - - request-handler-ref="requestHandler"/> <2> + + + - - ---- ====== -<1> Configure `CookieCsrfTokenRepository` with `HttpOnly` set to `false` so the cookie can be read by the JavaScript application. -<2> Configure a custom `CsrfTokenRequestHandler` that resolves the CSRF token based on whether it is an HTTP request header (`X-XSRF-TOKEN`) or request parameter (`_csrf`). - This implementation also causes the deferred `CsrfToken` to be loaded on every request, which will return a new cookie if needed. - [[csrf-integration-javascript-mpa]] ==== Multi-Page Applications From 596449d882693c0eabef144e8fc59d68055b12f9 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 27 May 2025 11:17:30 -0600 Subject: [PATCH 062/149] Polish Issue gh-14149 --- .../web/configurers/CsrfConfigurer.java | 19 ++----------------- .../web/configurers/CsrfConfigurerTests.java | 2 +- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java index 833efe4e22..6188a7f056 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -397,7 +397,7 @@ public final class CsrfConfigurer> } - private static class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler { + private static final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler { private final CsrfTokenRequestAttributeHandler plain = new CsrfTokenRequestAttributeHandler(); @@ -409,27 +409,12 @@ public final class CsrfConfigurer> @Override public void handle(HttpServletRequest request, HttpServletResponse response, Supplier csrfToken) { - /* - * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection - * of the CsrfToken when it is rendered in the response body. - */ this.xor.handle(request, response, csrfToken); } @Override public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { String headerValue = request.getHeader(csrfToken.getHeaderName()); - /* - * If the request contains a request header, use - * CsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies - * when a single-page application includes the header value automatically, - * which was obtained via a cookie containing the raw CsrfToken. - * - * In all other cases (e.g. if the request contains a request parameter), use - * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies - * when a server-side rendered form includes the _csrf request parameter as a - * hidden input. - */ return (StringUtils.hasText(headerValue) ? this.plain : this.xor).resolveCsrfTokenValue(request, csrfToken); } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java index 04a288078f..a37a101fbc 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 91b21663dba059005ff7432faf2aa08977f8d3d8 Mon Sep 17 00:00:00 2001 From: Ferenc Kemeny Date: Thu, 8 May 2025 22:40:47 +0200 Subject: [PATCH 063/149] Polish JwtTimestampValidatorTests This commit corrects the test that checks for both nbf and exp missing. It also adds one for just exp and on for just nbf. Issue gh-17004 Signed-off-by: Ferenc Kemeny --- .../oauth2/jwt/JwtTimestampValidatorTests.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java index 72164cf21b..272004a5ec 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java @@ -129,6 +129,23 @@ public class JwtTimestampValidatorTests { @Test public void validateWhenNeitherExpiryNorNotBeforeIsSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> { + c.remove(JwtClaimNames.EXP); + c.remove(JwtClaimNames.NBF); + }).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + + @Test + public void validateWhenExpiryIsSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.EXP)).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + + @Test + public void validateWhenNotBeforeIsSpecifiedThenReturnsSuccessfulResult() { Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.EXP)).build(); JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); From bf05b8b4307fe11fb7001aabd48835a0ffb6dea9 Mon Sep 17 00:00:00 2001 From: Ferenc Kemeny Date: Thu, 8 May 2025 22:41:36 +0200 Subject: [PATCH 064/149] Support Requiring exp and nbf in JwtTimestampsValidator Closes gh-17004 Signed-off-by: Ferenc Kemeny --- .../oauth2/jwt/JwtTimestampValidator.java | 45 +++++++++++++++---- .../jwt/JwtTimestampValidatorTests.java | 18 +++++++- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java index d191b8b11a..f23dfbe4f7 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ 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.ObjectUtils; /** * An implementation of {@link OAuth2TokenValidator} for verifying claims in a Jwt-based @@ -54,6 +55,10 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator { private final Duration clockSkew; + private boolean allowEmptyExpiryClaim = true; + + private boolean allowEmptyNotBeforeClaim = true; + private Clock clock = Clock.systemUTC(); /** @@ -68,30 +73,54 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator { this.clockSkew = clockSkew; } + /** + * Whether to allow the {@code exp} header to be empty. The default value is + * {@code true} + * + * @since 7.0 + */ + public void setAllowEmptyExpiryClaim(boolean allowEmptyExpiryClaim) { + this.allowEmptyExpiryClaim = allowEmptyExpiryClaim; + } + + /** + * Whether to allow the {@code nbf} header to be empty. The default value is + * {@code true} + * + * @since 7.0 + */ + public void setAllowEmptyNotBeforeClaim(boolean allowEmptyNotBeforeClaim) { + this.allowEmptyNotBeforeClaim = allowEmptyNotBeforeClaim; + } + @Override public OAuth2TokenValidatorResult validate(Jwt jwt) { Assert.notNull(jwt, "jwt cannot be null"); Instant expiry = jwt.getExpiresAt(); + if (!this.allowEmptyExpiryClaim && ObjectUtils.isEmpty(expiry)) { + return createOAuth2Error("exp is required"); + } if (expiry != null) { if (Instant.now(this.clock).minus(this.clockSkew).isAfter(expiry)) { - OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt expired at %s", jwt.getExpiresAt())); - return OAuth2TokenValidatorResult.failure(oAuth2Error); + return createOAuth2Error(String.format("Jwt expired at %s", jwt.getExpiresAt())); } } Instant notBefore = jwt.getNotBefore(); + if (!this.allowEmptyNotBeforeClaim && ObjectUtils.isEmpty(notBefore)) { + return createOAuth2Error("nbf is required"); + } if (notBefore != null) { if (Instant.now(this.clock).plus(this.clockSkew).isBefore(notBefore)) { - OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt used before %s", jwt.getNotBefore())); - return OAuth2TokenValidatorResult.failure(oAuth2Error); + return createOAuth2Error(String.format("Jwt used before %s", jwt.getNotBefore())); } } return OAuth2TokenValidatorResult.success(); } - private OAuth2Error createOAuth2Error(String reason) { + private OAuth2TokenValidatorResult createOAuth2Error(String reason) { this.logger.debug(reason); - return new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, reason, - "https://tools.ietf.org/html/rfc6750#section-3.1"); + return OAuth2TokenValidatorResult.failure(new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, reason, + "https://tools.ietf.org/html/rfc6750#section-3.1")); } /** diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java index 272004a5ec..0d88794c0f 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -158,6 +158,22 @@ public class JwtTimestampValidatorTests { assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); } + @Test + public void validateWhenNotAllowEmptyExpiryClaimAndNotBeforeIsValidAndExpiryIsNotSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.EXP)).notBefore(Instant.MIN).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); + jwtValidator.setAllowEmptyExpiryClaim(false); + assertThat(jwtValidator.validate(jwt).hasErrors()).isTrue(); + } + + @Test + public void validateWhenNotAllowEmptyNotBeforeClaimAndNotBeforeIsNotSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.NBF)).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); + jwtValidator.setAllowEmptyNotBeforeClaim(false); + assertThat(jwtValidator.validate(jwt).hasErrors()).isTrue(); + } + @Test public void validateWhenExpiryIsValidAndNotBeforeIsNotSpecifiedThenReturnsSuccessfulResult() { Jwt jwt = TestJwts.jwt().build(); From ec05e65668022b3c9768451e9d5bb33332d1f010 Mon Sep 17 00:00:00 2001 From: Maximilian Klose Date: Wed, 14 May 2025 08:01:15 +0200 Subject: [PATCH 065/149] Add Equals and HashCode methods for better comparison. Closes gh-16394 Signed-off-by: Maximilian Klose --- .../endpoint/OAuth2AuthorizationRequest.java | 30 ++++++++++++++++++- .../OAuth2AuthorizationRequestTests.java | 23 ++++++++++++++ .../TestOAuth2AuthorizationRequests.java | 11 +++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java index ff40a30785..37e323b5e4 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -188,6 +189,33 @@ public final class OAuth2AuthorizationRequest implements Serializable { return new Builder(AuthorizationGrantType.AUTHORIZATION_CODE); } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + OAuth2AuthorizationRequest that = (OAuth2AuthorizationRequest) obj; + + return Objects.equals(this.authorizationUri, that.authorizationUri) + && Objects.equals(this.authorizationGrantType, that.authorizationGrantType) + && Objects.equals(this.responseType, that.responseType) && Objects.equals(this.clientId, that.clientId) + && Objects.equals(this.redirectUri, that.redirectUri) && Objects.equals(this.scopes, that.scopes) + && Objects.equals(this.state, that.state) + && Objects.equals(this.additionalParameters, that.additionalParameters) + && Objects.equals(this.authorizationRequestUri, that.authorizationRequestUri) + && Objects.equals(this.attributes, that.attributes); + } + + @Override + public int hashCode() { + return Objects.hash(this.authorizationUri, this.clientId, this.authorizationGrantType, this.responseType, + this.redirectUri, this.scopes, this.state, this.additionalParameters, this.authorizationRequestUri, + this.attributes); + } + /** * Returns a new {@link Builder}, initialized with the values from the provided * {@code authorizationRequest}. diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java index 1c4365560d..9d4b089f1e 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java @@ -365,4 +365,27 @@ public class OAuth2AuthorizationRequestTests { + "item1=null&item2=value2"); } + @Test + public void equalsWhenAllFieldsEqualEqualsThenTrue() { + OAuth2AuthorizationRequest authorizationRequest1 = TestOAuth2AuthorizationRequests.allFields().build(); + + OAuth2AuthorizationRequest authorizationRequest2 = TestOAuth2AuthorizationRequests.allFields().build(); + + assertThat(authorizationRequest1).isNotSameAs(authorizationRequest2); + assertThat(authorizationRequest1).isEqualTo(authorizationRequest2); + } + + @Test + public void hashCodeWhenAllFieldsEqualThenHashCodesAreEqual() { + OAuth2AuthorizationRequest authorizationRequest1 = TestOAuth2AuthorizationRequests.allFields().build(); + + OAuth2AuthorizationRequest authorizationRequest2 = TestOAuth2AuthorizationRequests.allFields().build(); + + int authorizationRequest1HashCode = authorizationRequest1.hashCode(); + int authorizationRequest2HashCode = authorizationRequest2.hashCode(); + + assertThat(authorizationRequest1).isNotSameAs(authorizationRequest2); + assertThat(authorizationRequest1HashCode).isEqualTo(authorizationRequest2HashCode); + } + } diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/TestOAuth2AuthorizationRequests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/TestOAuth2AuthorizationRequests.java index eaf559a28d..c95f0771b3 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/TestOAuth2AuthorizationRequests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/TestOAuth2AuthorizationRequests.java @@ -47,4 +47,15 @@ public final class TestOAuth2AuthorizationRequests { return request().scope("openid"); } + public static OAuth2AuthorizationRequest.Builder allFields() { + // @formatter:off + return request() + .authorizationRequestUri("https://example.com") + .additionalParameters(Map.of("someAdditionalParameterKey", "someAdditionalParameterValue")) + .parameters((parametersMap) -> + parametersMap.put("someParameterKey", "someParameterValue")) + .scope("someScope"); + // @formatter:on + } + } From 42790403da164175c43589b147c03fcc4afd3a0a Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Fri, 18 Apr 2025 17:37:48 +0800 Subject: [PATCH 066/149] Use SpringReactiveOpaqueTokenIntrospector Now that NimbusReactiveOpaqueTokenIntrospector is deprecated, this commit changes the Spring Security default to now use SpringReactiveOpaqueTokenIntrospector. Issue gh-15988 Signed-off-by: Yanming Zhou --- .../config/web/server/ServerHttpSecurity.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) 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 a6d77b3683..a4063fced4 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 @@ -298,6 +298,7 @@ import org.springframework.web.util.pattern.PathPatternParser; * @author Parikshit Dutta * @author Ankur Pathak * @author Alexey Nesterov + * @author Yanming Zhou * @since 5.0 */ public class ServerHttpSecurity { @@ -5443,8 +5444,11 @@ public class ServerHttpSecurity { public OpaqueTokenSpec introspectionUri(String introspectionUri) { Assert.hasText(introspectionUri, "introspectionUri cannot be empty"); this.introspectionUri = introspectionUri; - this.introspector = () -> new SpringReactiveOpaqueTokenIntrospector(this.introspectionUri, - this.clientId, this.clientSecret); + this.introspector = () -> SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri(this.introspectionUri) + .clientId(this.clientId) + .clientSecret(this.clientSecret) + .build(); return this; } @@ -5459,8 +5463,11 @@ public class ServerHttpSecurity { Assert.notNull(clientSecret, "clientSecret cannot be null"); this.clientId = clientId; this.clientSecret = clientSecret; - this.introspector = () -> new SpringReactiveOpaqueTokenIntrospector(this.introspectionUri, - this.clientId, this.clientSecret); + this.introspector = () -> SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri(this.introspectionUri) + .clientId(this.clientId) + .clientSecret(this.clientSecret) + .build(); return this; } From da2d9aa8682138b4f436f9be4874447ee9e2acc9 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 28 May 2025 14:01:46 -0600 Subject: [PATCH 067/149] Add Username Property to Exception Closes gh-17179 --- .../UsernameNotFoundException.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/core/src/main/java/org/springframework/security/core/userdetails/UsernameNotFoundException.java b/core/src/main/java/org/springframework/security/core/userdetails/UsernameNotFoundException.java index d1d969dc26..eff0e13adb 100644 --- a/core/src/main/java/org/springframework/security/core/userdetails/UsernameNotFoundException.java +++ b/core/src/main/java/org/springframework/security/core/userdetails/UsernameNotFoundException.java @@ -31,12 +31,15 @@ public class UsernameNotFoundException extends AuthenticationException { @Serial private static final long serialVersionUID = 1410688585992297006L; + private final String name; + /** * Constructs a UsernameNotFoundException with the specified message. * @param msg the detail message. */ public UsernameNotFoundException(String msg) { super(msg); + this.name = null; } /** @@ -47,6 +50,42 @@ public class UsernameNotFoundException extends AuthenticationException { */ public UsernameNotFoundException(String msg, Throwable cause) { super(msg, cause); + this.name = null; + } + + private UsernameNotFoundException(String msg, String name, Throwable cause) { + super(msg, cause); + this.name = name; + } + + /** + * Construct an exception based on a specific username + * @param username the invalid username + * @return the {@link UsernameNotFoundException} + * @since 7.0 + */ + public static UsernameNotFoundException fromUsername(String username) { + return fromUsername(username, null); + } + + /** + * Construct an exception based on a specific username + * @param username the invalid username + * @param cause any underlying cause + * @return the {@link UsernameNotFoundException} + * @since 7.0 + */ + public static UsernameNotFoundException fromUsername(String username, Throwable cause) { + return new UsernameNotFoundException("user not found", username, cause); + } + + /** + * Get the username that couldn't be found + * @return the username + * @since 7.0 + */ + public String getName() { + return this.name; } } From 215547f8c8ae1455a24b6cbc8f897bfc0c10ea4b Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 28 May 2025 14:13:02 -0600 Subject: [PATCH 068/149] Use UsernameNotFoundException Factory Issue gh-17179 --- .../security/provisioning/InMemoryUserDetailsManager.java | 2 +- .../ldap/authentication/PasswordComparisonAuthenticator.java | 2 +- .../ad/ActiveDirectoryLdapAuthenticationProvider.java | 3 +-- .../security/ldap/search/FilterBasedLdapUserSearch.java | 2 +- .../security/ldap/userdetails/LdapUserDetailsManager.java | 2 +- .../ReactivePreAuthenticatedAuthenticationManager.java | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java b/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java index 498e165342..0a17b16547 100644 --- a/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java +++ b/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java @@ -164,7 +164,7 @@ public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetai public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDetails user = this.users.get(username.toLowerCase(Locale.ROOT)); if (user == null) { - throw new UsernameNotFoundException("user '" + username + "' not found"); + throw UsernameNotFoundException.fromUsername(username); } if (user instanceof CredentialsContainer) { return user; diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/PasswordComparisonAuthenticator.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/PasswordComparisonAuthenticator.java index 7d79d358ef..e158e87235 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/PasswordComparisonAuthenticator.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/PasswordComparisonAuthenticator.java @@ -93,7 +93,7 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic } } if (user == null) { - throw new UsernameNotFoundException("User not found: " + username); + throw UsernameNotFoundException.fromUsername(username); } if (logger.isTraceEnabled()) { logger.trace(LogMessage.format("Comparing password attribute '%s' for user '%s'", 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 6d43a67202..0ee5a54f0a 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 @@ -307,8 +307,7 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda throw ex; } // If we found no results, then the username/password did not match - UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException( - "User " + username + " not found in directory.", ex); + UsernameNotFoundException userNameNotFoundException = UsernameNotFoundException.fromUsername(username, ex); throw badCredentials(userNameNotFoundException); } } diff --git a/ldap/src/main/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearch.java b/ldap/src/main/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearch.java index 326b5c5fa1..2277b45463 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearch.java +++ b/ldap/src/main/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearch.java @@ -104,7 +104,7 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch { } catch (IncorrectResultSizeDataAccessException ex) { if (ex.getActualSize() == 0) { - throw new UsernameNotFoundException("User " + username + " not found in directory."); + throw UsernameNotFoundException.fromUsername(username); } // Search should never return multiple results if properly configured throw ex; 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 53d46980ac..3d945a0805 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 @@ -154,7 +154,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx)); } catch (NameNotFoundException ex) { - throw new UsernameNotFoundException("User " + username + " not found", ex); + throw UsernameNotFoundException.fromUsername(username, ex); } }); } diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/ReactivePreAuthenticatedAuthenticationManager.java b/web/src/main/java/org/springframework/security/web/server/authentication/ReactivePreAuthenticatedAuthenticationManager.java index 83cc524283..2528157f60 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/ReactivePreAuthenticatedAuthenticationManager.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/ReactivePreAuthenticatedAuthenticationManager.java @@ -62,7 +62,7 @@ public class ReactivePreAuthenticatedAuthenticationManager implements ReactiveAu .filter(this::supports) .map(Authentication::getName) .flatMap(this.userDetailsService::findByUsername) - .switchIfEmpty(Mono.error(() -> new UsernameNotFoundException("User not found"))) + .switchIfEmpty(Mono.error(() -> UsernameNotFoundException.fromUsername(authentication.getName()))) .doOnNext(this.userDetailsChecker::check) .map((userDetails) -> { PreAuthenticatedAuthenticationToken result = new PreAuthenticatedAuthenticationToken(userDetails, From 37a814bc29ff97026220a7c4e697639480374d65 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 28 May 2025 15:48:17 -0600 Subject: [PATCH 069/149] Add 7.0 -> 8.0 Migration Guide Closes gh-17182 --- docs/modules/ROOT/nav.adoc | 13 +- .../pages/migration-7/authentication.adoc | 68 --- .../ROOT/pages/migration-7/authorization.adoc | 24 - .../ROOT/pages/migration-7/configuration.adoc | 125 ----- docs/modules/ROOT/pages/migration-7/ldap.adoc | 11 - .../ROOT/pages/migration-7/oauth2.adoc | 172 ------ .../modules/ROOT/pages/migration-7/saml2.adoc | 165 ------ docs/modules/ROOT/pages/migration-7/web.adoc | 523 ------------------ .../{migration-7 => migration-8}/index.adoc | 8 +- docs/modules/ROOT/pages/migration/index.adoc | 32 +- .../ROOT/pages/migration/reactive.adoc | 97 ---- .../migration/servlet/authentication.adoc | 187 ------- .../migration/servlet/authorization.adoc | 136 ----- .../pages/migration/servlet/exploits.adoc | 44 -- .../ROOT/pages/migration/servlet/oauth2.adoc | 80 +++ .../migration/servlet/session-management.adoc | 49 -- 16 files changed, 98 insertions(+), 1636 deletions(-) delete mode 100644 docs/modules/ROOT/pages/migration-7/authentication.adoc delete mode 100644 docs/modules/ROOT/pages/migration-7/authorization.adoc delete mode 100644 docs/modules/ROOT/pages/migration-7/configuration.adoc delete mode 100644 docs/modules/ROOT/pages/migration-7/ldap.adoc delete mode 100644 docs/modules/ROOT/pages/migration-7/oauth2.adoc delete mode 100644 docs/modules/ROOT/pages/migration-7/saml2.adoc delete mode 100644 docs/modules/ROOT/pages/migration-7/web.adoc rename docs/modules/ROOT/pages/{migration-7 => migration-8}/index.adoc (52%) delete mode 100644 docs/modules/ROOT/pages/migration/servlet/authentication.adoc delete mode 100644 docs/modules/ROOT/pages/migration/servlet/authorization.adoc delete mode 100644 docs/modules/ROOT/pages/migration/servlet/exploits.adoc create mode 100644 docs/modules/ROOT/pages/migration/servlet/oauth2.adoc delete mode 100644 docs/modules/ROOT/pages/migration/servlet/session-management.adoc diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 09130e387e..595e9a4531 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -2,14 +2,11 @@ * xref:prerequisites.adoc[Prerequisites] * xref:community.adoc[Community] * xref:whats-new.adoc[What's New] -* xref:migration-7/index.adoc[Preparing for 7.0] -** xref:migration-7/authentication.adoc[Authentication] -** xref:migration-7/authorization.adoc[Authorization] -** xref:migration-7/configuration.adoc[Configuration] -** xref:migration-7/ldap.adoc[LDAP] -** xref:migration-7/oauth2.adoc[OAuth 2.0] -** xref:migration-7/web.adoc[Web] -* xref:migration/index.adoc[Migrating to 6] +* xref:migration-8/index.adoc[Preparing for 8.0] +* xref:migration/index.adoc[Migrating to 7] +** xref:migration/servlet/index.adoc[Servlet] +*** xref:migration/servlet/oauth2.adoc[OAuth 2.0] +** xref:migration/reactive.adoc[Reactive] * xref:getting-spring-security.adoc[Getting Spring Security] * xref:attachment$api/java/index.html[Javadoc] * xref:features/index.adoc[Features] diff --git a/docs/modules/ROOT/pages/migration-7/authentication.adoc b/docs/modules/ROOT/pages/migration-7/authentication.adoc deleted file mode 100644 index 9c5407ae00..0000000000 --- a/docs/modules/ROOT/pages/migration-7/authentication.adoc +++ /dev/null @@ -1,68 +0,0 @@ -= Authentication Changes - -== Opaque Token Credentials Will Be Encoded For You - -In order to comply more closely with the Introspection RFC, Spring Security's opaque token support will encode the client id and secret before creating the authorization header. -This change means you will no longer have to encode the client id and secret yourself. - -If your client id or secret contain URL-unsafe characters, then you can prepare yourself for this change by doing the following: - -=== Replace Usage of `introspectionClientCredentials` - -Since Spring Security can now do the encoding for you, replace xref:servlet/oauth2/resource-server/opaque-token.adoc#oauth2resourceserver-opaque-introspectionuri-dsl[using `introspectionClientCredentials`] with publishing the following `@Bean`: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -OpaqueTokenIntrospector introspector() { - return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) - .clientId(unencodedClientId).clientSecret(unencodedClientSecret).build(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun introspector(): OpaqueTokenIntrospector { - return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) - .clientId(unencodedClientId).clientSecret(unencodedClientSecret).build() -} ----- -====== - -The above will be the default in 7.0. - -If this setting gives you trouble or you cannot apply it for now, you can use the `RestOperations` constructor instead: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -OpaqueTokenIntrospector introspector() { - RestTemplate rest = new RestTemplate(); - rest.addInterceptor(new BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret)); - return new SpringOpaqueTokenIntrospector(introspectionUri, rest); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun introspector(): OpaqueTokenIntrospector { - val rest = RestTemplate() - rest.addInterceptor(BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret)) - return SpringOpaqueTokenIntrospector(introspectionUri, rest) -} ----- -====== diff --git a/docs/modules/ROOT/pages/migration-7/authorization.adoc b/docs/modules/ROOT/pages/migration-7/authorization.adoc deleted file mode 100644 index 031c8a8bfb..0000000000 --- a/docs/modules/ROOT/pages/migration-7/authorization.adoc +++ /dev/null @@ -1,24 +0,0 @@ -= Authorization Changes - -The following sections relate to how to adapt to changes in the authorization support. - -== Method Security - -[[compile-with-parameters]] -=== Compile With `-parameters` - -Spring Framework 6.1 https://github.com/spring-projects/spring-framework/issues/29559[removes LocalVariableTableParameterNameDiscoverer]. -This affects how `@PreAuthorize` and other xref:servlet/authorization/method-security.adoc[method security] annotations will process parameter names. -If you are using method security annotations with parameter names, for example: - -[source,java] -.Method security annotation using `id` parameter name ----- -@PreAuthorize("@authz.checkPermission(#id, authentication)") -public void doSomething(Long id) { - // ... -} ----- - -You must compile with `-parameters` to ensure that the parameter names are available at runtime. -For more information about this, please visit the https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#core-container[Upgrading to Spring Framework 6.1 page]. diff --git a/docs/modules/ROOT/pages/migration-7/configuration.adoc b/docs/modules/ROOT/pages/migration-7/configuration.adoc deleted file mode 100644 index 0a06694f95..0000000000 --- a/docs/modules/ROOT/pages/migration-7/configuration.adoc +++ /dev/null @@ -1,125 +0,0 @@ -= Configuration Migrations - -The following steps relate to changes around how to configure `HttpSecurity`, `WebSecurity` and related components. - -== Use the Lambda DSL - -The Lambda DSL is present in Spring Security since version 5.2, and it allows HTTP security to be configured using lambdas. - -You may have seen this style of configuration in the Spring Security documentation or samples. -Let us take a look at how a lambda configuration of HTTP security compares to the previous configuration style. - -[source,java] -.Configuration using lambdas ----- -@Configuration -@EnableWebSecurity -public class SecurityConfig { - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/blog/**").permitAll() - .anyRequest().authenticated() - ) - .formLogin(formLogin -> formLogin - .loginPage("/login") - .permitAll() - ) - .rememberMe(Customizer.withDefaults()); - - return http.build(); - } -} ----- - -[source,java] -.Equivalent configuration without using lambdas ----- -@Configuration -@EnableWebSecurity -public class SecurityConfig { - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests() - .requestMatchers("/blog/**").permitAll() - .anyRequest().authenticated() - .and() - .formLogin() - .loginPage("/login") - .permitAll() - .and() - .rememberMe(); - - return http.build(); - } -} ----- - -The Lambda DSL is the preferred way to configure Spring Security, the prior configuration style will not be valid in Spring Security 7 where the usage of the Lambda DSL will be required. -This has been done mainly for a couple of reasons: - -- The previous way it was not clear what object was getting configured without knowing what the return type was. -The deeper the nesting the more confusing it became. -Even experienced users would think that their configuration was doing one thing when in fact, it was doing something else. - -- Consistency. -Many code bases switched between the two styles which caused inconsistencies that made understanding the configuration difficult and often led to misconfigurations. - -=== Lambda DSL Configuration Tips - -When comparing the two samples above, you will notice some key differences: - -- In the Lambda DSL there is no need to chain configuration options using the `.and()` method. -The `HttpSecurity` instance is automatically returned for further configuration after the call to the lambda method. - -- `Customizer.withDefaults()` enables a security feature using the defaults provided by Spring Security. -This is a shortcut for the lambda expression `it -> {}`. - -=== WebFlux Security - -You may also configure WebFlux security using lambdas in a similar manner. -Below is an example configuration using lambdas. - -[source,java] -.WebFlux configuration using lambdas ----- -@Configuration -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { - http - .authorizeExchange(exchanges -> exchanges - .pathMatchers("/blog/**").permitAll() - .anyExchange().authenticated() - ) - .httpBasic(Customizer.withDefaults()) - .formLogin(formLogin -> formLogin - .loginPage("/login") - ); - - return http.build(); - } - -} ----- - -=== Goals of the Lambda DSL - -The Lambda DSL was created to accomplish to following goals: - -- Automatic indentation makes the configuration more readable. -- There is no need to chain configuration options using `.and()` -- The Spring Security DSL has a similar configuration style to other Spring DSLs such as Spring Integration and Spring Cloud Gateway. - -== Use `.with()` instead of `.apply()` for Custom DSLs - -In versions prior to 6.2, if you had a xref:servlet/configuration/java.adoc#jc-custom-dsls[custom DSL], you would apply it to the `HttpSecurity` using the `HttpSecurity#apply(...)` method. -However, starting from version 6.2, this method is deprecated and will be removed in 7.0 because it will no longer be possible to chain configurations using `.and()` once `.and()` is removed (see https://github.com/spring-projects/spring-security/issues/13067). -Instead, it is recommended to use the new `.with(...)` method. -For more information about how to use `.with(...)` please refer to the xref:servlet/configuration/java.adoc#jc-custom-dsls[Custom DSLs section]. diff --git a/docs/modules/ROOT/pages/migration-7/ldap.adoc b/docs/modules/ROOT/pages/migration-7/ldap.adoc deleted file mode 100644 index 3bef91f9ac..0000000000 --- a/docs/modules/ROOT/pages/migration-7/ldap.adoc +++ /dev/null @@ -1,11 +0,0 @@ -= LDAP Migrations - -The following steps relate to changes around how to configure the LDAP components and how to use an embedded LDAP server. - -== Use `UnboundId` instead of `ApacheDS` - -ApacheDS has not had a GA release for a considerable period, and its classes in Spring Security were https://github.com/spring-projects/spring-security/pull/6376[deprecated in version 5.2]. -Consequently, support for ApacheDS will be discontinued in version 7.0. - -If you are currently using ApacheDS as an embedded LDAP server, we recommend migrating to https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundId]. -You can find instructions in xref:servlet/authentication/passwords/ldap.adoc#servlet-authentication-ldap-embedded[this section] that describe how to set up an embedded UnboundId LDAP server. diff --git a/docs/modules/ROOT/pages/migration-7/oauth2.adoc b/docs/modules/ROOT/pages/migration-7/oauth2.adoc deleted file mode 100644 index 1c3b9b43e2..0000000000 --- a/docs/modules/ROOT/pages/migration-7/oauth2.adoc +++ /dev/null @@ -1,172 +0,0 @@ -= OAuth 2.0 Changes - -== Validate `typ` Header with `JwtTypeValidator` - -`NimbusJwtDecoder` in Spring Security 7 will move `typ` header validation to `JwtTypeValidator` instead of relying on Nimbus. -This brings it in line with `NimbusJwtDecoder` validating claims instead of relying on Nimbus to validate them. - -If you are changing Nimbus's default type validation in a `jwtProcessorCustomizer` method, then you should move that to `JwtTypeValidator` or an implementation of `OAuth2TokenValidator` of your own. - -To check if you are prepared for this change, add the default `JwtTypeValidator` to your list of validators, as this will be included by default in 7: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -JwtDecoder jwtDecoder() { - NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) - .validateTypes(false) <1> - // ... your remaining configuration - .build(); - jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( - new JwtIssuerValidator(location), JwtTypeValidator.jwt())); <2> - return jwtDecoder; -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun jwtDecoder(): JwtDecoder { - val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) - .validateTypes(false) <1> - // ... your remaining configuration - .build() - jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( - JwtIssuerValidator(location), JwtTypeValidator.jwt())) <2> - return jwtDecoder -} ----- -====== -<1> - Switch off Nimbus verifying the `typ` (this will be off by default in 7) -<2> - Add the default `typ` validator (this will be included by default in 7) - -Note the default value verifies that the `typ` value either be `JWT` or not present, which is the same as the Nimbus default. -It is also aligned with https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.9[RFC 7515] which states that `typ` is optional. - - -=== I'm Using A `DefaultJOSEObjectTypeVerifier` - -If you have something like the following in your configuration: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -JwtDecoder jwtDecoder() { - NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) - .jwtProcessorCustomizer((c) -> c - .setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>("JOSE")) - ) - .build(); - return jwtDecoder; -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun jwtDecoder(): JwtDecoder { - val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) - .jwtProcessorCustomizer { - it.setJWSTypeVerifier(DefaultJOSEObjectTypeVerifier("JOSE")) - } - .build() - return jwtDecoder -} ----- -====== - -Then change this to: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -JwtDecoder jwtDecoder() { - NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) - .validateTypes(false) - .build(); - jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( - new JwtIssuerValidator(location), new JwtTypeValidator("JOSE"))); - return jwtDecoder; -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun jwtDecoder(): JwtDecoder { - val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) - .validateTypes(false) - .build() - jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( - JwtIssuerValidator(location), JwtTypeValidator("JOSE"))) - return jwtDecoder -} ----- -====== - -To indicate that the `typ` header is optional, use `#setAllowEmpty(true)` (this is the equivalent of including `null` in the list of allowed types in `DefaultJOSEObjectTypeVerifier`). - -=== I want to opt-out - -If you want to keep doing things the way that you are, then the steps are similar, just in reverse: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -JwtDecoder jwtDecoder() { - NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) - .validateTypes(true) <1> - .jwtProcessorCustomizer((c) -> c - .setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>("JOSE")) - ) - .build(); - jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>( - new JwtTimestampValidator(), new JwtIssuerValidator(location))); <2> - return jwtDecoder; -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun jwtDecoder(): JwtDecoder { - val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) - .validateTypes(true) <1> - .jwtProcessorCustomizer { - it.setJWSTypeVerifier(DefaultJOSEObjectTypeVerifier("JOSE")) - } - .build() - jwtDecoder.setJwtValidator(DelegatingOAuth2TokenValidator( - JwtTimestampValidator(), JwtIssuerValidator(location))) <2> - return jwtDecoder -} ----- -====== -<1> - leave Nimbus type verification on -<2> - specify the list of validators you need, excluding `JwtTypeValidator` - -For additional guidance, please see the xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-validation[JwtDecoder Validators] section in the reference. diff --git a/docs/modules/ROOT/pages/migration-7/saml2.adoc b/docs/modules/ROOT/pages/migration-7/saml2.adoc deleted file mode 100644 index eed236a64b..0000000000 --- a/docs/modules/ROOT/pages/migration-7/saml2.adoc +++ /dev/null @@ -1,165 +0,0 @@ -= Saml 2.0 Migrations - -== Continue Filter Chain When No Relying Party Found - -In Spring Security 6, `Saml2WebSsoAuthenticationFilter` throws an exception when the request URI matches, but no relying party registration is found. - -There are a number of cases when an application would not consider this an error situation. -For example, this filter doesn't know how the `AuthorizationFilter` will respond to a missing relying party. -In some cases it may be allowable. - -In other cases, you may want your `AuthenticationEntryPoint` to be invoked, which would happen if this filter were to allow the request to continue to the `AuthorizationFilter`. - -To improve this filter's flexibility, in Spring Security 7 it will continue the filter chain when there is no relying party registration found instead of throwing an exception. - -For many applications, the only notable change will be that your `authenticationEntryPoint` will be invoked if the relying party registration cannot be found. -When you have only one asserting party, this means by default a new authentication request will be built and sent back to the asserting party, which may cause a "Too Many Redirects" loop. - -To see if you are affected in this way, you can prepare for this change in 6 by setting the following property in `Saml2WebSsoAuthenticationFilter`: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -http - .saml2Login((saml2) -> saml2 - .withObjectPostProcessor(new ObjectPostProcessor() { - @Override - public Saml2WebSsoAuthenticationFilter postProcess(Saml2WebSsoAuthenticationFilter filter) { - filter.setContinueChainWhenNoRelyingPartyRegistrationFound(true); - return filter; - } - }) - ) ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - saml2Login { } - withObjectPostProcessor( - object : ObjectPostProcessor() { - override fun postProcess(filter: Saml2WebSsoAuthenticationFilter): Saml2WebSsoAuthenticationFilter { - filter.setContinueChainWhenNoRelyingPartyRegistrationFound(true) - return filter - } - }) -} ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - ----- -====== - -== Validate Response After Validating Assertions - -In Spring Security 6, the order of authenticating a `` is as follows: - -1. Verify the Response Signature, if any -2. Decrypt the Response -3. Validate Response attributes, like Destination and Issuer -4. For each assertion, verify the signature, decrypt, and then validate its fields -5. Check to ensure that the response has at least one assertion with a name field - -This ordering sometimes poses challenges since some response validation is being done in Step 3 and some in Step 5. -Specifically, this poses a chellenge when an application doesn't have a name field and doesn't need it to be validated. - -In Spring Security 7, this is simplified by moving response validation to after assertion validation and combining the two separate validation steps 3 and 5. -When this is complete, response validation will no longer check for the existence of the `NameID` attribute and rely on ``ResponseAuthenticationConverter``s to do this. - -This will add support ``ResponseAuthenticationConverter``s that don't use the `NameID` element in their `Authentication` instance and so don't need it validated. - -To opt-in to this behavior in advance, use `OpenSaml5AuthenticationProvider#setValidateResponseAfterAssertions` to `true` like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider(); -provider.setValidateResponseAfterAssertions(true); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val provider = OpenSaml5AuthenticationProvider() -provider.setValidateResponseAfterAssertions(true) ----- -====== - -This will change the authentication steps as follows: - -1. Verify the Response Signature, if any -2. Decrypt the Response -3. For each assertion, verify the signature, decrypt, and then validate its fields -4. Validate Response attributes, like Destination and Issuer - -Note that if you have a custom response authentication converter, then you are now responsible to check if the `NameID` element exists in the event that you need it. - -Alternatively to updating your response authentication converter, you can specify a custom `ResponseValidator` that adds back in the check for the `NameID` element as follows: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider(); -provider.setValidateResponseAfterAssertions(true); -ResponseValidator responseValidator = ResponseValidator.withDefaults((responseToken) -> { - Response response = responseToken.getResponse(); - Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); - Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, - "Assertion [" + firstAssertion.getID() + "] is missing a subject"); - Saml2ResponseValidationResult failed = Saml2ResponseValidationResult.failure(error); - if (assertion.getSubject() == null) { - return failed; - } - if (assertion.getSubject().getNameID() == null) { - return failed; - } - if (assertion.getSubject().getNameID().getValue() == null) { - return failed; - } - return Saml2ResponseValidationResult.success(); -}); -provider.setResponseValidator(responseValidator); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val provider = OpenSaml5AuthenticationProvider() -provider.setValidateResponseAfterAssertions(true) -val responseValidator = ResponseValidator.withDefaults { responseToken: ResponseToken -> - val response = responseToken.getResponse() - val assertion = CollectionUtils.firstElement(response.getAssertions()) - val error = Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, - "Assertion [" + firstAssertion.getID() + "] is missing a subject") - val failed = Saml2ResponseValidationResult.failure(error) - if (assertion.getSubject() == null) { - return@withDefaults failed - } - if (assertion.getSubject().getNameID() == null) { - return@withDefaults failed - } - if (assertion.getSubject().getNameID().getValue() == null) { - return@withDefaults failed - } - return@withDefaults Saml2ResponseValidationResult.success() -} -provider.setResponseValidator(responseValidator) ----- -====== diff --git a/docs/modules/ROOT/pages/migration-7/web.adoc b/docs/modules/ROOT/pages/migration-7/web.adoc deleted file mode 100644 index 467f2663e8..0000000000 --- a/docs/modules/ROOT/pages/migration-7/web.adoc +++ /dev/null @@ -1,523 +0,0 @@ -= Web Migrations - -== Favor Relative URIs - -When redirecting to a login endpoint, Spring Security has favored absolute URIs in the past. -For example, if you set your login page like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -http - // ... - .formLogin((form) -> form.loginPage("/my-login")) - // ... ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - formLogin { - loginPage = "/my-login" - } -} ----- - -Xml:: -+ -[source,kotlin,role="secondary"] ----- - - - ----- -====== - -then when redirecting to `/my-login` Spring Security would use a `Location:` like the following: - -[source] ----- -302 Found -// ... -Location: https://myapp.example.org/my-login ----- - -However, this is no longer necessary given that the RFC is was based on is now obsolete. - -In Spring Security 7, this is changed to use a relative URI like so: - -[source] ----- -302 Found -// ... -Location: /my-login ----- - -Most applications will not notice a difference. -However, in the event that this change causes problems, you can switch back to the Spring Security 6 behavior by setting the `favorRelativeUrls` value: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -LoginUrlAuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint("/my-login"); -entryPoint.setFavorRelativeUris(false); -http - // ... - .exceptionHandling((exceptions) -> exceptions.authenticaitonEntryPoint(entryPoint)) - // ... ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -LoginUrlAuthenticationEntryPoint entryPoint = LoginUrlAuthenticationEntryPoint("/my-login") -entryPoint.setFavorRelativeUris(false) - -http { - exceptionHandling { - authenticationEntryPoint = entryPoint - } -} ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - - - - - - - ----- -====== - -== PortResolver - -Spring Security uses an API called `PortResolver` to provide a workaround for a bug in Internet Explorer. -The workaround is no longer necessary and can cause users problems in some scenarios. -For this reason, Spring Security 7 will remove the `PortResolver` interface. - -To prepare for this change, users should expose the `PortResolver.NO_OP` as a Bean named `portResolver`. -This ensures that the `PortResolver` implementation that is used is a no-op (e.g. does nothing) which simulates the removal of `PortResolver`. -An example configuration can be found below: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -PortResolver portResolver() { - return PortResolver.NO_OP; -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun portResolver(): PortResolver { - return PortResolver.NO_OP -} ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - - ----- -====== - -[[use-path-pattern]] -== Use PathPatternRequestMatcher by Default - -In Spring Security 7, `AntPathRequestMatcher` and `MvcRequestMatcher` are no longer supported and the Java DSL requires that all URIs be absolute (less any context root). -At that time, Spring Security 7 will use `PathPatternRequestMatcher` by default. - -To check how prepared you are for this change, you can publish this bean: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - return new PathPatternRequestMatcherBuilderFactoryBean(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun requestMatcherBuilder(): PathPatternRequestMatcherBuilderFactoryBean { - return PathPatternRequestMatcherBuilderFactoryBean() -} ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - ----- -====== - -This will tell the Spring Security DSL to use `PathPatternRequestMatcher` for all request matchers that it constructs. - -In the event that you are directly constructing an object (as opposed to having the DSL construct it) that has a `setRequestMatcher` method. you should also proactively specify a `PathPatternRequestMatcher` there as well. - -=== Migrate `exitUserUrl` and `switchUserUrl` Request Matchers in `SwitchUserFilter` - -`SwitchUserFilter`, constructs an `AntPathRequestMatcher` in its `setExitUserUrl` and `setSwitchUserUrl` methods. -This will change to use `PathPatternRequestMatcher` in Spring Security 7. - -To prepare for this change, call `setExitUserMatcher` and `setSwithcUserMatcher` to provide this `PathPatternRequestMatcher` in advance. -That is, change this: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -SwitchUserFilter switchUser = new SwitchUserFilter(); -// ... other configuration -switchUser.setExitUserUrl("/exit/impersonate"); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val switchUser = SwitchUserFilter() -// ... other configuration -switchUser.setExitUserUrl("/exit/impersonate") ----- -====== - -to this: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -SwitchUserFilter switchUser = new SwitchUserFilter(); -// ... other configuration -switchUser.setExitUserMatcher(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, "/exit/impersonate")); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val switchUser = SwitchUserFilter() -// ... other configuration -switchUser.setExitUserMatcher(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, "/exit/impersonate")) ----- -====== - -=== Migrate `filterProcessingUrl` Request Matcher in `AbstractAuthenticationProcessingFilter` Implementations - -Spring Security 6 converts any processing endpoint configured through `setFilterProcessingUrl` to an `AntPathRequestMatcher`. -In Spring Security 7, this will change to `PathPatternRequestMatcher`. - -If you are directly invoking `setFilterProcessingUrl` on a filter that extends `AbstractAuthenticationProcessingFilter`, like `UsernamePasswordAuthenticationFilter`, `OAuth2LoginAuthenticationFilter`, `Saml2WebSsoAuthenticationFilter`, `OneTimeTokenAuthenticationFilter`, or `WebAuthnAuthenticationFilter`, call `setRequiredAuthenticationRequestMatcher` instead to provide this `PathPatternRequestMatcher` in advance. - -That is, change this: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -UsernamePasswordAuthenticationFilter usernamePassword = new UsernamePasswordAuthenticationFilter(authenticationManager); -usernamePassword.setFilterProcessingUrl("/my/processing/url"); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val usernamePassword = UsernamePasswordAuthenticationFilter(authenticationManager) -usernamePassword.setFilterProcessingUrl("/my/processing/url") ----- -====== - -to this: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -UsernamePasswordAuthenticationFilter usernamePassword = new UsernamePasswordAuthenticationFilter(authenticationManager); -RequestMatcher requestMatcher = PathPatternRequestMatcher.withDefaults().matcher("/my/processing/url"); -usernamePassword.setRequest(requestMatcher); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val usernamePassword = UsernamePasswordAuthenticationFilter(authenticationManager) -val requestMatcher = PathPatternRequestMatcher.withDefaults().matcher("/my/processing/url") -usernamePassword.setRequest(requestMatcher) ----- -====== - -[NOTE] ------ -Most applications use the DSL instead of setting the `filterProcessingUrl` directly on a filter instance. ------ - -=== Migrate CAS Proxy Receptor Request Matcher - -Spring Security 6 converts any configured `proxyReceptorUrl` to a request matcher that matches the end of the request, that is `/**/proxy/receptor`. -In Spring Security 7, this pattern is not allowed and will change to using `PathPatternRequestMatcher`. -Also in Spring Security 7m the URL should by absolute, excluding any context path, like so: `/proxy/receptor`. - -So to prepare for these change, you can use `setProxyReceptorRequestMatcher` instead of `setProxyReceptorUrl`. - -That is, change this: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -casAuthentication.setProxyReceptorUrl("/proxy/receptor"); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -casAuthentication.setProxyReceptorUrl("/proxy/receptor") ----- -====== - -to this: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -casAuthentication.setProxyReceptorUrl(PathPatternRequestMatcher.withDefaults().matcher("/proxy/receptor")); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -casAuthentication.setProxyReceptorUrl(PathPatternRequestMatcher.withDefaults().matcher("/proxy/receptor")) ----- -====== - -=== Migrate your WebInvocationPrivilegeEvaluator - -If you are using Spring Security's JSP Taglibs or are using `WebInvocationPrivilegeEvaluator` directly, be aware of the following changes: - -1. `RequestMatcherWebInvocationPrivilegeEvaluator` is deprecated in favor of `AuthorizationManagerWebInvocationPrivilegeEvaluator` -2. `HandlerMappingIntrospectorRequestTransformer` is deprecated in favor of `PathPatternRequestTransformer` - -If you are not constructing these directly, you can opt-in to both changes in advance by publishing a `PathPatternRequestTransformer` like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -HttpServletRequestTransformer pathPatternRequestTransformer() { - return new PathPatternRequestTransformer(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun pathPatternRequestTransformer(): HttpServletRequestTransformer { - return PathPatternRequestTransformer() -} ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - ----- -====== - -Spring Security will take this as a signal to use the new implementations. - -[[NOTE]] ----- -One difference you may notice is that `AuthorizationManagerWebPrivilegeInvocationEvaluator` allows the authentication to be `null` if the authorization rule is `permitAll`. - -Test your endpoints that `permitAll` in case JSP requests using this same require should not, in fact, be permitted. ----- - -== Include the Servlet Path Prefix in Authorization Rules - -For many applications <> will make no difference since most commonly all URIs listed are matched by the default servlet. - -However, if you have other servlets with servlet path prefixes, xref:servlet/authorization/authorize-http-requests.adoc[then these paths now need to be supplied separately]. - -For example, if I have a Spring MVC controller with `@RequestMapping("/orders")` and my MVC application is deployed to `/mvc` (instead of the default servlet), then the URI for this endpoint is `/mvc/orders`. -Historically, the Java DSL hasn't had a simple way to specify the servlet path prefix and Spring Security attempted to infer it. - -Over time, we learned that these inference would surprise developers. -Instead of taking this responsibility away from developers, now it is simpler to specify the servlet path prefix like so: - -[method,java] ----- -PathPatternRequestParser.Builder servlet = PathPatternRequestParser.withDefaults().basePath("/mvc"); -http - .authorizeHttpRequests((authorize) -> authorize - .requestMatchers(servlet.pattern("/orders/**").matcher()).authenticated() - ) ----- - - -For paths that belong to the default servlet, use `PathPatternRequestParser.withDefaults()` instead: - -[method,java] ----- -PathPatternRequestParser.Builder request = PathPatternRequestParser.withDefaults(); -http - .authorizeHttpRequests((authorize) -> authorize - .requestMatchers(request.pattern("/js/**").matcher()).authenticated() - ) ----- - -Note that this doesn't address every kind of servlet since not all servlets have a path prefix. -For example, expressions that match the JSP Servlet might use an ant pattern `/**/*.jsp`. - -There is not yet a general-purpose replacement for these, and so you are encouraged to use `RegexRequestMatcher`, like so: `regexMatcher("\\.jsp$")`. - -For many applications this will make no difference since most commonly all URIs listed are matched by the default servlet. - -[[use-redirect-to-https]] -== Use RedirectToHttps Instead of Channel Security - -Years ago, HTTPS at large was enough of a performance and configuration concern that applications wanted to be able to decide which segments of an application would require HTTPS. - -`requires-channel` in XML and `requiresChannel` in Java Config allowed configurating an application with that in mind: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -http - .requiresChannel((channel) -> channel - .requestMatchers("/secure/**").requiresSecureChannel() - .requestMatchers("/insecure/**").requiresInsecureChannel() - ) ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -http { - requiresChannel { - secure("/secure/**") - seccure("/insecure/**", "REQUIRES_INSECURE_CHANNEL") - } -} ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - - - - ----- -====== - -Modern applications should either always require HTTPS. -However, there are times, like when developing locally, when one would like the application to use HTTP. -Or, you may have continuing circumstances that require part of your application to be HTTP. - -In any case, you can migrate to `redirect-to-https-request-matcher-ref` and `redirectToHttps` by first constructing a `RequestMatcher` that contains all circumstances where redirecting to HTTPS is needed. -Then you can reference that request matcher like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -http - .redirectToHttps((https) -> https.requestMatchers("/secure/**")) - // ... ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -var secure: RequestMatcher = PathPatternRequestMatcher.withDefaults().pattern("/secure/**") -http { - redirectToHttps { - requestMatchers = secure - } - // ... -} ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - - - - - - - - - ----- -====== - -[TIP] -===== -If you have several circumstances where HTTP is needed, consider using `OrRequestMatcher` to combine them into a single `RequestMatcher` instance. -===== diff --git a/docs/modules/ROOT/pages/migration-7/index.adoc b/docs/modules/ROOT/pages/migration-8/index.adoc similarity index 52% rename from docs/modules/ROOT/pages/migration-7/index.adoc rename to docs/modules/ROOT/pages/migration-8/index.adoc index 9cdb6dfda5..fe4b8cba89 100644 --- a/docs/modules/ROOT/pages/migration-7/index.adoc +++ b/docs/modules/ROOT/pages/migration-8/index.adoc @@ -1,9 +1,9 @@ [[preparing]] -= Preparing for 7.0 += Preparing for 8.0 :page-section-summary-toc: 1 -While Spring Security 7.0 does not have a release date yet, it is important to start preparing for it now. +While Spring Security 8.0 does not have a release date yet, it is important to start preparing for it now. -This preparation guide is designed to summarize the biggest changes in Spring Security 7.0 and provide steps to prepare for them. +This preparation guide is designed to summarize the biggest changes in Spring Security 8.0 and provide steps to prepare for them. -It is important to keep your application up to date with the latest Spring Security 6 and Spring Boot 3 releases. +It is important to keep your application up to date with the latest Spring Security 7 and Spring Boot 4 releases. diff --git a/docs/modules/ROOT/pages/migration/index.adoc b/docs/modules/ROOT/pages/migration/index.adoc index db1d8d1333..4ad6c991df 100644 --- a/docs/modules/ROOT/pages/migration/index.adoc +++ b/docs/modules/ROOT/pages/migration/index.adoc @@ -1,34 +1,20 @@ [[migration]] -= Migrating to 6.0 += Migrating to 7.0 :spring-security-reference-base-url: https://docs.spring.io/spring-security/reference -The Spring Security team has prepared the 5.8 release to simplify upgrading to Spring Security 6.0. -Use 5.8 and -ifdef::spring-security-version[] -{spring-security-reference-base-url}/5.8/migration/index.html[its preparation steps] -endif::[] -ifndef::spring-security-version[] -its preparation steps -endif::[] -to simplify updating to 6.0. +Spring Security 6.5 is the last release in the 6.x generation of Spring Security. +It provides strategies for configuring breaking changes to use the 7.0 way before updating. +We recommend you use 6.5 and {spring-security-reference-base-url}/6.5/migration-7/index.html[its preparation steps] to simplify updating to 7.0. -After updating to 5.8, follow this guide to perform any remaining migration or cleanup steps. +After updating to 6.5, follow this guide to perform any remaining migration or cleanup steps. And recall that if you run into trouble, the preparation guide includes opt-out steps to revert to 5.x behaviors. -== Update to Spring Security 6 +== Update to Spring Security 7 -The first step is to ensure you are the latest patch release of Spring Boot 3.0. -Next, you should ensure you are on the latest patch release of Spring Security 6. -For directions, on how to update to Spring Security 6 visit the xref:getting-spring-security.adoc[] section of the reference guide. - -== Update Package Names - -Now that you are updated, you need to change your `javax` imports to `jakarta` imports. - -== Compile With `--parameters` - -If you are using method parameter names in `@PreAuthorize`, `@PostAuthorize`, or any other method security annotations, you may need to xref:migration/servlet/authorization.adoc#compile-with-parameters[compile with `-parameters`]. +The first step is to ensure you are the latest patch release of Spring Boot 4.0. +Next, you should ensure you are on the latest patch release of Spring Security 7. +For directions, on how to update to Spring Security 7 visit the xref:getting-spring-security.adoc[] section of the reference guide. == Perform Application-Specific Steps diff --git a/docs/modules/ROOT/pages/migration/reactive.adoc b/docs/modules/ROOT/pages/migration/reactive.adoc index 370e52262f..17d33ff586 100644 --- a/docs/modules/ROOT/pages/migration/reactive.adoc +++ b/docs/modules/ROOT/pages/migration/reactive.adoc @@ -1,100 +1,3 @@ = Reactive If you have already performed the xref:migration/index.adoc[initial migration steps] for your Reactive application, you're now ready to perform steps specific to Reactive applications. - -== Use `AuthorizationManager` for Method Security - -In 6.0, `@EnableReactiveMethodSecurity` defaults `useAuthorizationManager` to `true`. -So, to complete migration, {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurity.html[`@EnableReactiveMethodSecurity`] remove the `useAuthorizationManager` attribute: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@EnableReactiveMethodSecurity(useAuthorizationManager = true) ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@EnableReactiveMethodSecurity(useAuthorizationManager = true) ----- -====== - -changes to: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@EnableReactiveMethodSecurity ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@EnableReactiveMethodSecurity ----- -====== - -== Propagate ``AuthenticationServiceException``s - -{security-api-url}org/springframework/security/web/server/authentication/AuthenticationWebFilter.html[`AuthenticationWebFilter`] propagates {security-api-url}org/springframework/security/authentication/AuthenticationServiceException.html[``AuthenticationServiceException``]s to the {security-api-url}org/springframework/security/web/server/ServerAuthenticationEntryPoint.html[`ServerAuthenticationEntryPoint`]. -Because ``AuthenticationServiceException``s represent a server-side error instead of a client-side error, in 6.0, this changes to propagate them to the container. - -So, if you opted into this behavior by setting `rethrowAuthenticationServiceException` too `true`, you can now remove it like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -AuthenticationFailureHandler bearerFailureHandler = new ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint); -bearerFailureHandler.setRethrowAuthenticationServiceException(true); -AuthenticationFailureHandler basicFailureHandler = new ServerAuthenticationEntryPointFailureHandler(basicEntryPoint); -basicFailureHandler.setRethrowAuthenticationServiceException(true); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val bearerFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint) -bearerFailureHandler.setRethrowAuthenticationServiceException(true) -val basicFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(basicEntryPoint) -basicFailureHandler.setRethrowAuthenticationServiceException(true) ----- -====== - -changes to: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -AuthenticationFailureHandler bearerFailureHandler = new ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint); -AuthenticationFailureHandler basicFailureHandler = new ServerAuthenticationEntryPointFailureHandler(basicEntryPoint); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val bearerFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint) -val basicFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(basicEntryPoint) ----- -====== - -[NOTE] -==== -If you configured the `ServerAuthenticationFailureHandler` only for the purpose of updating to 6.0, you can remove it completely. -==== diff --git a/docs/modules/ROOT/pages/migration/servlet/authentication.adoc b/docs/modules/ROOT/pages/migration/servlet/authentication.adoc deleted file mode 100644 index 3db94cf7a6..0000000000 --- a/docs/modules/ROOT/pages/migration/servlet/authentication.adoc +++ /dev/null @@ -1,187 +0,0 @@ -= Authentication Migrations - -The following steps relate to how to finish migrating authentication support. - -== Propagate ``AuthenticationServiceException``s - -{security-api-url}org/springframework/security/web/authentication/AuthenticationFilter.html[`AuthenticationFilter`] propagates {security-api-url}org/springframework/security/authentication/AuthenticationServiceException.html[``AuthenticationServiceException``]s to the {security-api-url}org/springframework/security/web/AuthenticationEntryPoint.html[`AuthenticationEntryPoint`]. -Because ``AuthenticationServiceException``s represent a server-side error instead of a client-side error, in 6.0, this changes to propagate them to the container. - -So, if you opted into this behavior by setting `rethrowAuthenticationServiceException` to `true`, you can now remove it like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -AuthenticationFilter authenticationFilter = new AuthenticationFilter(...); -AuthenticationEntryPointFailureHandler handler = new AuthenticationEntryPointFailureHandler(...); -handler.setRethrowAuthenticationServiceException(true); -authenticationFilter.setAuthenticationFailureHandler(handler); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val authenticationFilter: AuthenticationFilter = AuthenticationFilter(...) -val handler: AuthenticationEntryPointFailureHandler = AuthenticationEntryPointFailureHandler(...) -handler.setRethrowAuthenticationServiceException(true) -authenticationFilter.setAuthenticationFailureHandler(handler) ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - - - - - - - - ----- -====== - -changes to: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -AuthenticationFilter authenticationFilter = new AuthenticationFilter(...); -AuthenticationEntryPointFailureHandler handler = new AuthenticationEntryPointFailureHandler(...); -authenticationFilter.setAuthenticationFailureHandler(handler); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val authenticationFilter: AuthenticationFilter = AuthenticationFilter(...) -val handler: AuthenticationEntryPointFailureHandler = AuthenticationEntryPointFailureHandler(...) -authenticationFilter.setAuthenticationFailureHandler(handler) ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - - - - - - - - ----- -====== - -[[servlet-opt-in-sha256-rememberme]] -== Use SHA-256 in Remember Me - -In 6.0, the `TokenBasedRememberMeServices` uses SHA-256 to encode and match the token. -To complete the migration, any default values can be removed. - -For example, if you opted in to the 6.0 default for `encodingAlgorithm` and `matchingAlgorithm` like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -@EnableWebSecurity -public class SecurityConfig { - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception { - http - // ... - .rememberMe((remember) -> remember - .rememberMeServices(rememberMeServices) - ); - return http.build(); - } - @Bean - RememberMeServices rememberMeServices(UserDetailsService userDetailsService) { - RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.SHA256; - TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm); - rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.SHA256); - return rememberMe; - } -} ----- - -XML:: -+ -[source,xml,role="secondary"] ----- - - - - - - - - - ----- -====== - -then the defaults can be removed: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Configuration -@EnableWebSecurity -public class SecurityConfig { - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception { - http - // ... - .rememberMe((remember) -> remember - .rememberMeServices(rememberMeServices) - ); - return http.build(); - } - @Bean - RememberMeServices rememberMeServices(UserDetailsService userDetailsService) { - return new TokenBasedRememberMeServices(myKey, userDetailsService); - } -} ----- - -XML:: -+ -[source,xml,role="secondary"] ----- - - - - - - - ----- -====== - -== Default authorities for oauth2Login() - -In Spring Security 5, the default `GrantedAuthority` given to a user that authenticates with an OAuth2 or OpenID Connect 1.0 provider (via `oauth2Login()`) is `ROLE_USER`. - -In Spring Security 6, the default authority given to a user authenticating with an OAuth2 provider is `OAUTH2_USER`. -The default authority given to a user authenticating with an OpenID Connect 1.0 provider is `OIDC_USER`. -If you configured the `GrantedAuthoritiesMapper` only for the purpose of updating to 6.0, you can remove it completely. diff --git a/docs/modules/ROOT/pages/migration/servlet/authorization.adoc b/docs/modules/ROOT/pages/migration/servlet/authorization.adoc deleted file mode 100644 index 0f60a2f4c1..0000000000 --- a/docs/modules/ROOT/pages/migration/servlet/authorization.adoc +++ /dev/null @@ -1,136 +0,0 @@ -= Authorization Migrations - -The following steps relate to how to finish migrating authorization support. - -== Use `AuthorizationManager` for Method Security - -There are no further migration steps for this feature. - -== Use `AuthorizationManager` for Message Security - -In 6.0, `` defaults `use-authorization-manager` to `true`. -So, to complete migration, remove any `websocket-message-broker@use-authorization-manager=true` attribute. - -For example: - -[tabs] -====== -Xml:: -+ -[source,xml,role="primary"] ----- - ----- -====== - -changes to: - -[tabs] -====== -Xml:: -+ -[source,xml,role="primary"] ----- - ----- -====== - -There are no further migrations steps for Java or Kotlin for this feature. - -== Use `AuthorizationManager` for Request Security - -In 6.0, `` defaults `once-per-request` to `false`, `filter-all-dispatcher-types` to `true`, and `use-authorization-manager` to `true`. -Also, xref:servlet/authorization/authorize-http-requests.adoc[`authorizeHttpRequests#filterAllDispatcherTypes`] defaults to `true`. -So, to complete migration, any defaults values can be removed. - -For example, if you opted in to the 6.0 default for `filter-all-dispatcher-types` or `authorizeHttpRequests#filterAllDispatcherTypes` like so: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize - .filterAllDispatcherTypes(true) - // ... - ) ----- - -Kotlin:: -+ -[source,java,role="secondary"] ----- -http { - authorizeHttpRequests { - filterAllDispatcherTypes = true - // ... - } -} ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - ----- -====== - -then the defaults may be removed: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -http - .authorizeHttpRequests((authorize) -> authorize - // ... - ) ----- - -Kotlin:: -+ -[source,java,role="secondary"] ----- -http { - authorizeHttpRequests { - // ... - } -} ----- - -Xml:: -+ -[source,xml,role="secondary"] ----- - ----- -====== - -[NOTE] -==== -`once-per-request` applies only when `use-authorization-manager="false"` and `filter-all-dispatcher-types` only applies when `use-authorization-manager="true"` -==== - -[[compile-with-parameters]] -=== Compile With `-parameters` - -Spring Framework 6.1 https://github.com/spring-projects/spring-framework/issues/29559[removes LocalVariableTableParameterNameDiscoverer]. -This affects how `@PreAuthorize` and other xref:servlet/authorization/method-security.adoc[method security] annotations will process parameter names. -If you are using method security annotations with parameter names, for example: - -[source,java] -.Method security annotation using `id` parameter name ----- -@PreAuthorize("@authz.checkPermission(#id, authentication)") -public void doSomething(Long id) { - // ... -} ----- - -You must compile with `-parameters` to ensure that the parameter names are available at runtime. -For more information about this, please visit the https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#core-container[Upgrading to Spring Framework 6.1 page]. diff --git a/docs/modules/ROOT/pages/migration/servlet/exploits.adoc b/docs/modules/ROOT/pages/migration/servlet/exploits.adoc deleted file mode 100644 index bddafec6c2..0000000000 --- a/docs/modules/ROOT/pages/migration/servlet/exploits.adoc +++ /dev/null @@ -1,44 +0,0 @@ -= Exploit Protection Migrations -:spring-security-reference-base-url: https://docs.spring.io/spring-security/reference - -The 5.8 migration guide contains several steps for -ifdef::spring-security-version[] -{spring-security-reference-base-url}/5.8/migration/servlet/exploits.html[exploit protection migrations] when updating to 6.0. -endif::[] -ifndef::spring-security-version[] -exploit protection migrations when updating to 6.0. -endif::[] -You are encouraged to follow those steps first. - -The following steps relate to how to finish migrating exploit protection support. - -== Defer Loading CsrfToken - -In Spring Security 5.8, the default `CsrfTokenRequestHandler` for making the `CsrfToken` available to the application is `CsrfTokenRequestAttributeHandler`. -The default for the field `csrfRequestAttributeName` is `null`, which causes the CSRF token to be loaded on every request. - -In Spring Security 6, `csrfRequestAttributeName` defaults to `_csrf`. -If you configured the following only for the purpose of updating to 6.0, you can now remove it: - - requestHandler.setCsrfRequestAttributeName("_csrf"); - -== Protect against CSRF BREACH - -In Spring Security 5.8, the default `CsrfTokenRequestHandler` for making the `CsrfToken` available to the application is `CsrfTokenRequestAttributeHandler`. -`XorCsrfTokenRequestAttributeHandler` was added to allow opting into CSRF BREACH support. - -In Spring Security 6, `XorCsrfTokenRequestAttributeHandler` is the default `CsrfTokenRequestHandler` for making the `CsrfToken` available. -If you configured the `XorCsrfTokenRequestAttributeHandler` only for the purpose of updating to 6.0, you can remove it completely. - -[NOTE] -==== -If you have set the `csrfRequestAttributeName` to `null` in order to opt out of deferred tokens, or if you have configured a `CsrfTokenRequestHandler` for any other reason, you can leave the configuration in place. -==== - -== CSRF BREACH with WebSocket support - -In Spring Security 5.8, the default `ChannelInterceptor` for making the `CsrfToken` available with xref:servlet/integrations/websocket.adoc[WebSocket Security] is `CsrfChannelInterceptor`. -`XorCsrfChannelInterceptor` was added to allow opting into CSRF BREACH support. - -In Spring Security 6, `XorCsrfChannelInterceptor` is the default `ChannelInterceptor` for making the `CsrfToken` available. -If you configured the `XorCsrfChannelInterceptor` only for the purpose of updating to 6.0, you can remove it completely. diff --git a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc new file mode 100644 index 0000000000..3caeec4133 --- /dev/null +++ b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc @@ -0,0 +1,80 @@ += OAuth 2.0 Migrations + +== Validate `typ` Header with `JwtTypeValidator` + +If when following the 6.5 preparatory steps you set `validateTypes` to `false`, you can now remove it. +You can also remove explicitly adding `JwtTypeValidator` to the list of defaults. + +For example, change this: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +JwtDecoder jwtDecoder() { + NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) + .validateTypes(false) <1> + // ... your remaining configuration + .build(); + jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( + new JwtIssuerValidator(location), JwtTypeValidator.jwt())); <2> + return jwtDecoder; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun jwtDecoder(): JwtDecoder { + val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) + .validateTypes(false) <1> + // ... your remaining configuration + .build() + jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( + JwtIssuerValidator(location), JwtTypeValidator.jwt())) <2> + return jwtDecoder +} +---- +====== +<1> - Switch off Nimbus verifying the `typ` +<2> - Add the default `typ` validator + +to this: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +JwtDecoder jwtDecoder() { + NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) + // ... your remaining configuration <1> + .build(); + jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(location)); <2> + return jwtDecoder; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun jwtDecoder(): JwtDecoder { + val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location) + // ... your remaining configuration + .build() + jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(location)) <2> + return jwtDecoder +} +---- +====== +<1> - `validateTypes` now defaults to `false` +<2> - `JwtTypeValidator#jwt` is added by all `createDefaultXXX` methods diff --git a/docs/modules/ROOT/pages/migration/servlet/session-management.adoc b/docs/modules/ROOT/pages/migration/servlet/session-management.adoc deleted file mode 100644 index c7409b9e07..0000000000 --- a/docs/modules/ROOT/pages/migration/servlet/session-management.adoc +++ /dev/null @@ -1,49 +0,0 @@ -= Session Management Migrations - -The following steps relate to how to finish migrating session management support. - -== Require Explicit Saving of SecurityContextRepository - -In Spring Security 5, the default behavior is for the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[`SecurityContext`] to automatically be saved to the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] using the xref:servlet/authentication/persistence.adoc#securitycontextpersistencefilter[`SecurityContextPersistenceFilter`]. -Saving must be done just prior to the `HttpServletResponse` being committed and just before `SecurityContextPersistenceFilter`. -Unfortunately, automatic persistence of the `SecurityContext` can surprise users when it is done prior to the request completing (i.e. just prior to committing the `HttpServletResponse`). -It also is complex to keep track of the state to determine if a save is necessary causing unnecessary writes to the `SecurityContextRepository` (i.e. `HttpSession`) at times. - -In Spring Security 6, the default behavior is that the xref:servlet/authentication/persistence.adoc#securitycontextholderfilter[`SecurityContextHolderFilter`] will only read the `SecurityContext` from `SecurityContextRepository` and populate it in the `SecurityContextHolder`. -Users now must explicitly save the `SecurityContext` with the `SecurityContextRepository` if they want the `SecurityContext` to persist between requests. -This removes ambiguity and improves performance by only requiring writing to the `SecurityContextRepository` (i.e. `HttpSession`) when it is necessary. - -[NOTE] -==== -Saving the context is also needed when clearing it out, for example during logout. Refer to this section to xref:servlet/authentication/session-management.adoc#properly-clearing-authentication[know more about that]. -==== - -If you are explicitly opting into Spring Security 6's new defaults, the following configuration can be removed to accept the Spring Security 6 defaults. - - -include::partial$servlet/architecture/security-context-explicit.adoc[] - -== Multiple SecurityContextRepository - -In Spring Security 5, the default xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] was `HttpSessionSecurityContextRepository`. - -In Spring Security 6, the default `SecurityContextRepository` is `DelegatingSecurityContextRepository`. -If you configured the `SecurityContextRepository` only for the purpose of updating to 6.0, you can remove it completely. - -== Deprecation in SecurityContextRepository - -There are no further migration steps for this deprecation. - -[[requestcache-query-optimization]] -== Optimize Querying of `RequestCache` - -In Spring Security 5, the default behavior is to query the xref:servlet/architecture.adoc#savedrequests[saved request] on every request. -This means that in a typical setup, that in order to use the xref:servlet/architecture.adoc#requestcache[`RequestCache`] the `HttpSession` is queried on every request. - -In Spring Security 6, the default is that `RequestCache` will only be queried for a cached request if the HTTP parameter `continue` is defined. -This allows Spring Security to avoid unnecessarily reading the `HttpSession` with the `RequestCache`. - -In Spring Security 5 the default is to use `HttpSessionRequestCache` which will be queried for a cached request on every request. -If you are not overriding the defaults (i.e. using `NullRequestCache`), then the following configuration can be used to explicitly opt into the Spring Security 6 behavior in Spring Security 5.8: - -include::partial$servlet/architecture/request-cache-continue.adoc[] From 6d3b54df21ec0cffc30c8b3e0784220bd117a87d Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 27 May 2025 13:13:54 -0600 Subject: [PATCH 070/149] Change Type Validation Default NimbusJwtDecoder and NimbusReactiveJwtDecoder now use Spring Security's JwtTypeValidator by default instead of Nimbus's type validator. Closes gh-17181 --- ...ckChannelLogoutAuthenticationProvider.java | 16 ++-- ...elLogoutReactiveAuthenticationManager.java | 15 ++-- .../ROOT/pages/migration/reactive.adoc | 79 +++++++++++++++++++ .../security/oauth2/jwt/JwtValidators.java | 8 +- .../security/oauth2/jwt/NimbusJwtDecoder.java | 6 +- .../oauth2/jwt/NimbusReactiveJwtDecoder.java | 8 +- .../oauth2/jwt/JwtValidatorsTests.java | 3 +- .../oauth2/jwt/NimbusJwtDecoderTests.java | 10 +-- .../jwt/NimbusReactiveJwtDecoderTests.java | 3 + 9 files changed, 111 insertions(+), 37 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutAuthenticationProvider.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutAuthenticationProvider.java index 07debd7420..3209736f51 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutAuthenticationProvider.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutAuthenticationProvider.java @@ -18,11 +18,6 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.cl import java.util.function.Function; -import com.nimbusds.jose.JOSEObjectType; -import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; -import com.nimbusds.jose.proc.JOSEObjectTypeVerifier; -import com.nimbusds.jose.proc.SecurityContext; - import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; @@ -38,6 +33,7 @@ import org.springframework.security.oauth2.jwt.BadJwtException; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoderFactory; +import org.springframework.security.oauth2.jwt.JwtTypeValidator; import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.util.Assert; @@ -67,8 +63,10 @@ final class OidcBackChannelLogoutAuthenticationProvider implements Authenticatio * Construct an {@link OidcBackChannelLogoutAuthenticationProvider} */ OidcBackChannelLogoutAuthenticationProvider() { + JwtTypeValidator type = new JwtTypeValidator("JWT", "logout+jwt"); + type.setAllowEmpty(true); Function> jwtValidator = (clientRegistration) -> JwtValidators - .createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration)); + .createDefaultWithValidators(type, new OidcBackChannelLogoutTokenValidator(clientRegistration)); this.logoutTokenDecoderFactory = (clientRegistration) -> { String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri(); if (!StringUtils.hasText(jwkSetUri)) { @@ -79,11 +77,7 @@ final class OidcBackChannelLogoutAuthenticationProvider implements Authenticatio null); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } - JOSEObjectTypeVerifier typeVerifier = new DefaultJOSEObjectTypeVerifier<>(null, - JOSEObjectType.JWT, new JOSEObjectType("logout+jwt")); - NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri) - .jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier)) - .build(); + NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build(); decoder.setJwtValidator(jwtValidator.apply(clientRegistration)); decoder.setClaimSetConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverter()); return decoder; diff --git a/config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java b/config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java index 04334be1bf..155d80f01a 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java +++ b/config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java @@ -18,10 +18,6 @@ package org.springframework.security.config.web.server; import java.util.function.Function; -import com.nimbusds.jose.JOSEObjectType; -import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; -import com.nimbusds.jose.proc.JOSEObjectTypeVerifier; -import com.nimbusds.jose.proc.JWKSecurityContext; import reactor.core.publisher.Mono; import org.springframework.security.authentication.AuthenticationProvider; @@ -41,6 +37,7 @@ import org.springframework.security.oauth2.jwt.BadJwtException; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoderFactory; +import org.springframework.security.oauth2.jwt.JwtTypeValidator; import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; @@ -72,8 +69,10 @@ final class OidcBackChannelLogoutReactiveAuthenticationManager implements Reacti * Construct an {@link OidcBackChannelLogoutReactiveAuthenticationManager} */ OidcBackChannelLogoutReactiveAuthenticationManager() { + JwtTypeValidator type = new JwtTypeValidator("JWT", "logout+jwt"); + type.setAllowEmpty(true); Function> jwtValidator = (clientRegistration) -> JwtValidators - .createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration)); + .createDefaultWithValidators(type, new OidcBackChannelLogoutTokenValidator(clientRegistration)); this.logoutTokenDecoderFactory = (clientRegistration) -> { String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri(); if (!StringUtils.hasText(jwkSetUri)) { @@ -84,11 +83,7 @@ final class OidcBackChannelLogoutReactiveAuthenticationManager implements Reacti null); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } - JOSEObjectTypeVerifier typeVerifier = new DefaultJOSEObjectTypeVerifier<>(null, - JOSEObjectType.JWT, new JOSEObjectType("logout+jwt")); - NimbusReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri) - .jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier)) - .build(); + NimbusReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build(); decoder.setJwtValidator(jwtValidator.apply(clientRegistration)); decoder.setClaimSetConverter( new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters())); diff --git a/docs/modules/ROOT/pages/migration/reactive.adoc b/docs/modules/ROOT/pages/migration/reactive.adoc index 17d33ff586..44caa91592 100644 --- a/docs/modules/ROOT/pages/migration/reactive.adoc +++ b/docs/modules/ROOT/pages/migration/reactive.adoc @@ -1,3 +1,82 @@ = Reactive If you have already performed the xref:migration/index.adoc[initial migration steps] for your Reactive application, you're now ready to perform steps specific to Reactive applications. + +== Validate `typ` Header with `JwtTypeValidator` + +If when following the 6.5 preparatory steps you set `validateTypes` to `false`, you can now remove it. +You can also remove explicitly adding `JwtTypeValidator` to the list of defaults. + +For example, change this: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +JwtDecoder jwtDecoder() { + NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(location) + .validateTypes(false) <1> + // ... your remaining configuration + .build(); + jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( + new JwtIssuerValidator(location), JwtTypeValidator.jwt())); <2> + return jwtDecoder; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun jwtDecoder(): JwtDecoder { + val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(location) + .validateTypes(false) <1> + // ... your remaining configuration + .build() + jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators( + JwtIssuerValidator(location), JwtTypeValidator.jwt())) <2> + return jwtDecoder +} +---- +====== +<1> - Switch off Nimbus verifying the `typ` +<2> - Add the default `typ` validator + +to this: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +NimbusReactiveJwtDecoder jwtDecoder() { + NimbusJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(location) + // ... your remaining configuration <1> + .build(); + jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(location)); <2> + return jwtDecoder; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun jwtDecoder(): NimbusReactiveJwtDecoder { + val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(location) + // ... your remaining configuration + .build() + jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(location)) <2> + return jwtDecoder +} +---- +====== +<1> - `validateTypes` now defaults to `false` +<2> - `JwtTypeValidator#jwt` is added by all `createDefaultXXX` methods 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 b958328c8d..a904684398 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 @@ -75,8 +75,8 @@ public final class JwtValidators { * supplied */ public static OAuth2TokenValidator createDefault() { - return new DelegatingOAuth2TokenValidator<>( - Arrays.asList(new JwtTimestampValidator(), new X509CertificateThumbprintValidator( + return new DelegatingOAuth2TokenValidator<>(Arrays.asList(JwtTypeValidator.jwt(), new JwtTimestampValidator(), + new X509CertificateThumbprintValidator( X509CertificateThumbprintValidator.DEFAULT_X509_CERTIFICATE_SUPPLIER))); } @@ -104,6 +104,10 @@ public final class JwtValidators { if (jwtTimestampValidator == null) { tokenValidators.add(0, new JwtTimestampValidator()); } + JwtTypeValidator typeValidator = CollectionUtils.findValueOfType(tokenValidators, JwtTypeValidator.class); + if (typeValidator == null) { + tokenValidators.add(0, JwtTypeValidator.jwt()); + } return new DelegatingOAuth2TokenValidator<>(tokenValidators); } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index eb5efbabec..66f0b70e45 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -279,7 +279,7 @@ public final class NimbusJwtDecoder implements JwtDecoder { private Function, Set> defaultAlgorithms = (source) -> Set .of(JWSAlgorithm.RS256); - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private final Set signatureAlgorithms = new HashSet<>(); @@ -548,7 +548,7 @@ public final class NimbusJwtDecoder implements JwtDecoder { private JWSAlgorithm jwsAlgorithm; - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private final RSAPublicKey key; @@ -680,7 +680,7 @@ public final class NimbusJwtDecoder implements JwtDecoder { private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256; - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private Consumer> jwtProcessorCustomizer; diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java index 32f97c1355..c76532689d 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java @@ -324,7 +324,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { private Function>> defaultAlgorithms = (source) -> Mono .just(Set.of(JWSAlgorithm.RS256)); - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private Set signatureAlgorithms = new HashSet<>(); @@ -547,7 +547,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { private JWSAlgorithm jwsAlgorithm; - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private Consumer> jwtProcessorCustomizer; @@ -682,7 +682,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256; - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private Consumer> jwtProcessorCustomizer; @@ -814,7 +814,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256; - private JOSEObjectTypeVerifier typeVerifier = JWT_TYPE_VERIFIER; + private JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; private Consumer> jwtProcessorCustomizer; 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 index 33174e9181..dad08edb69 100644 --- 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 @@ -62,7 +62,8 @@ public class JwtValidatorsTests { assertThat(containsByType(validator, JwtTimestampValidator.class)).isTrue(); assertThat(containsByType(validator, X509CertificateThumbprintValidator.class)).isTrue(); - assertThat(Objects.requireNonNull(tokenValidators).size()).isEqualTo(2); + assertThat(containsByType(validator, JwtTypeValidator.class)).isTrue(); + assertThat(Objects.requireNonNull(tokenValidators).size()).isEqualTo(3); } @Test diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java index 9b7805a0d8..91db639c45 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java @@ -458,10 +458,8 @@ public class NimbusJwtDecoderTests { // @formatter:off NimbusJwtDecoder decoder = NimbusJwtDecoder.withPublicKey(publicKey) .signatureAlgorithm(SignatureAlgorithm.RS256) - .jwtProcessorCustomizer((p) -> p - .setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS"))) - ) .build(); + decoder.setJwtValidator(JwtValidators.createDefaultWithValidators(new JwtTypeValidator("JWS"))); // @formatter:on assertThat(decoder.decode(signedJwt.serialize()).hasClaim(JwtClaimNames.EXP)).isNotNull(); } @@ -575,10 +573,8 @@ public class NimbusJwtDecoderTests { // @formatter:off NimbusJwtDecoder decoder = NimbusJwtDecoder.withSecretKey(secretKey) .macAlgorithm(MacAlgorithm.HS256) - .jwtProcessorCustomizer((p) -> p - .setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("JWS"))) - ) .build(); + decoder.setJwtValidator(JwtValidators.createDefaultWithValidators(new JwtTypeValidator("JWS"))); // @formatter:on assertThat(decoder.decode(signedJwt.serialize()).hasClaim(JwtClaimNames.EXP)).isNotNull(); } @@ -837,6 +833,7 @@ public class NimbusJwtDecoderTests { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY) .validateType(false) .build(); + jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success()); RSAPrivateKey privateKey = TestKeys.DEFAULT_PRIVATE_KEY; SignedJWT jwt = signedJwt(privateKey, new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(), @@ -849,6 +846,7 @@ public class NimbusJwtDecoderTests { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY) .validateType(false) .build(); + jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success()); SignedJWT jwt = signedJwt(TestKeys.DEFAULT_SECRET_KEY, new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JOSE).build(), new JWTClaimsSet.Builder().subject("subject").build()); diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java index 6ec57ab2cc..bee51921a6 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java @@ -667,6 +667,7 @@ public class NimbusReactiveJwtDecoderTests { NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY) .validateType(false) .build(); + jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success()); RSAPrivateKey privateKey = TestKeys.DEFAULT_PRIVATE_KEY; SignedJWT jwt = signedJwt(privateKey, new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(), @@ -679,6 +680,7 @@ public class NimbusReactiveJwtDecoderTests { NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY) .validateType(false) .build(); + jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success()); SignedJWT jwt = signedJwt(TestKeys.DEFAULT_SECRET_KEY, new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JOSE).build(), new JWTClaimsSet.Builder().subject("subject").build()); @@ -693,6 +695,7 @@ public class NimbusReactiveJwtDecoderTests { NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSource((jwt) -> Flux.just(jwk)) .validateType(false) .build(); + jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success()); SignedJWT jwt = signedJwt(TestKeys.DEFAULT_PRIVATE_KEY, new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(), new JWTClaimsSet.Builder().subject("subject").build()); From fd4f06a66e06d4dc586c7f6ba06499eb65879e7e Mon Sep 17 00:00:00 2001 From: Evgeniy Cheban Date: Thu, 17 Apr 2025 19:25:15 +0300 Subject: [PATCH 071/149] Support Spring Data container types for AuthorizeReturnObject Closes gh-15994 Signed-off-by: Evgeniy Cheban --- .../AuthorizationProxyDataConfiguration.java | 52 +++++++- ...ePostMethodSecurityConfigurationTests.java | 112 ++++++++++++++++-- 2 files changed, 152 insertions(+), 12 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyDataConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyDataConfiguration.java index e446ee2736..092bb83a8d 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyDataConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyDataConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,22 @@ package org.springframework.security.config.annotation.method.configuration; +import java.util.List; + import org.springframework.aop.framework.AopInfrastructureBean; 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.core.Ordered; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.SliceImpl; +import org.springframework.data.geo.GeoPage; +import org.springframework.data.geo.GeoResult; +import org.springframework.data.geo.GeoResults; import org.springframework.security.aot.hint.SecurityHintsRegistrar; import org.springframework.security.authorization.AuthorizationProxyFactory; +import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar; @Configuration(proxyBeanMethods = false) @@ -34,4 +43,45 @@ final class AuthorizationProxyDataConfiguration implements AopInfrastructureBean return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory); } + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + DataTargetVisitor dataTargetVisitor() { + return new DataTargetVisitor(); + } + + private static final class DataTargetVisitor implements AuthorizationAdvisorProxyFactory.TargetVisitor, Ordered { + + private static final int DEFAULT_ORDER = 200; + + @Override + public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) { + if (target instanceof GeoResults geoResults) { + return new GeoResults<>(proxyFactory.proxy(geoResults.getContent()), geoResults.getAverageDistance()); + } + if (target instanceof GeoResult geoResult) { + return new GeoResult<>(proxyFactory.proxy(geoResult.getContent()), geoResult.getDistance()); + } + if (target instanceof GeoPage geoPage) { + GeoResults results = new GeoResults<>(proxyFactory.proxy(geoPage.getContent()), + geoPage.getAverageDistance()); + return new GeoPage<>(results, geoPage.getPageable(), geoPage.getTotalElements()); + } + if (target instanceof PageImpl page) { + List content = proxyFactory.proxy(page.getContent()); + return new PageImpl<>(content, page.getPageable(), page.getTotalElements()); + } + if (target instanceof SliceImpl slice) { + List content = proxyFactory.proxy(slice.getContent()); + return new SliceImpl<>(content, slice.getPageable(), slice.hasNext()); + } + return null; + } + + @Override + public int getOrder() { + return DEFAULT_ORDER; + } + + } + } 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 fe9ccfc77e..b8e46ffea0 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 @@ -63,7 +63,14 @@ import org.springframework.context.annotation.Role; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationConfigurationException; -import org.springframework.core.annotation.Order; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.GeoPage; +import org.springframework.data.geo.GeoResult; +import org.springframework.data.geo.GeoResults; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; @@ -756,6 +763,28 @@ public class PrePostMethodSecurityConfigurationTests { assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude); } + @Test + @WithMockUser(authorities = "airplane:read") + public void findGeoResultByIdWhenAuthorizedResultThenAuthorizes() { + this.spring.register(AuthorizeResultConfig.class).autowire(); + FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); + GeoResult geoResultFlight = flights.findGeoResultFlightById("1"); + Flight flight = geoResultFlight.getContent(); + assertThatNoException().isThrownBy(flight::getAltitude); + assertThatNoException().isThrownBy(flight::getSeats); + } + + @Test + @WithMockUser(authorities = "seating:read") + public void findGeoResultByIdWhenUnauthorizedResultThenDenies() { + this.spring.register(AuthorizeResultConfig.class).autowire(); + FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); + GeoResult geoResultFlight = flights.findGeoResultFlightById("1"); + Flight flight = geoResultFlight.getContent(); + assertThatNoException().isThrownBy(flight::getSeats); + assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude); + } + @Test @WithMockUser(authorities = "airplane:read") public void findByIdWhenAuthorizedResponseEntityThenAuthorizes() { @@ -827,6 +856,46 @@ public class PrePostMethodSecurityConfigurationTests { .doesNotContain("Kevin Mitnick")); } + @Test + @WithMockUser(authorities = "airplane:read") + public void findPageWhenPostFilterThenFilters() { + this.spring.register(AuthorizeResultConfig.class).autowire(); + FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); + flights.findPage() + .forEach((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName) + .doesNotContain("Kevin Mitnick")); + } + + @Test + @WithMockUser(authorities = "airplane:read") + public void findSliceWhenPostFilterThenFilters() { + this.spring.register(AuthorizeResultConfig.class).autowire(); + FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); + flights.findSlice() + .forEach((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName) + .doesNotContain("Kevin Mitnick")); + } + + @Test + @WithMockUser(authorities = "airplane:read") + public void findGeoPageWhenPostFilterThenFilters() { + this.spring.register(AuthorizeResultConfig.class).autowire(); + FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); + flights.findGeoPage() + .forEach((flight) -> assertThat(flight.getContent().getPassengers()).extracting(Passenger::getName) + .doesNotContain("Kevin Mitnick")); + } + + @Test + @WithMockUser(authorities = "airplane:read") + public void findGeoResultsWhenPostFilterThenFilters() { + this.spring.register(AuthorizeResultConfig.class).autowire(); + FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class); + flights.findGeoResults() + .forEach((flight) -> assertThat(flight.getContent().getPassengers()).extracting(Passenger::getName) + .doesNotContain("Kevin Mitnick")); + } + @Test @WithMockUser(authorities = "airplane:read") public void findAllWhenPreFilterThenFilters() { @@ -1762,16 +1831,8 @@ public class PrePostMethodSecurityConfigurationTests { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - @Order(1) - static TargetVisitor mock() { - return Mockito.mock(TargetVisitor.class); - } - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - @Order(0) - static TargetVisitor skipValueTypes() { - return TargetVisitor.defaultsSkipValueTypes(); + static TargetVisitor customTargetVisitor() { + return TargetVisitor.of(Mockito.mock(), TargetVisitor.defaultsSkipValueTypes()); } @Bean @@ -1802,10 +1863,39 @@ public class PrePostMethodSecurityConfigurationTests { return this.flights.values().iterator(); } + Page findPage() { + return new PageImpl<>(new ArrayList<>(this.flights.values())); + } + + Slice findSlice() { + return new SliceImpl<>(new ArrayList<>(this.flights.values())); + } + + GeoPage findGeoPage() { + List> results = new ArrayList<>(); + for (Flight flight : this.flights.values()) { + results.add(new GeoResult<>(flight, new Distance(flight.altitude))); + } + return new GeoPage<>(new GeoResults<>(results)); + } + + GeoResults findGeoResults() { + List> results = new ArrayList<>(); + for (Flight flight : this.flights.values()) { + results.add(new GeoResult<>(flight, new Distance(flight.altitude))); + } + return new GeoResults<>(results); + } + Flight findById(String id) { return this.flights.get(id); } + GeoResult findGeoResultFlightById(String id) { + Flight flight = this.flights.get(id); + return new GeoResult<>(flight, new Distance(flight.altitude)); + } + Flight save(Flight flight) { this.flights.put(flight.getId(), flight); return flight; From 5517d8fe3a2b4d2ea85e1e06b42e55caaadb04a8 Mon Sep 17 00:00:00 2001 From: Pat McCusker Date: Fri, 16 May 2025 18:33:36 -0400 Subject: [PATCH 072/149] Deprecate the X5T JOSE Header name Closes gh-16979 Signed-off-by: Pat McCusker --- .../security/oauth2/jwt/JoseHeader.java | 18 +++++++++++++++++- .../security/oauth2/jwt/JoseHeaderNames.java | 10 +++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JoseHeader.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JoseHeader.java index 3b749fe395..38c41f29bd 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JoseHeader.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JoseHeader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -119,7 +119,15 @@ class JoseHeader { * thumbprint (a.k.a. digest) of the DER encoding of the X.509 certificate * corresponding to the key used to digitally sign the JWS or encrypt the JWE. * @return the X.509 certificate SHA-1 thumbprint + * @deprecated The SHA-1 algorithm has been proven to be vulnerable to collision + * attacks and should not be used. See the
Google + * Security Blog for more info. + * @see Announcing + * the first SHA1 collision */ + @Deprecated public String getX509SHA1Thumbprint() { return getHeader(JoseHeaderNames.X5T); } @@ -271,7 +279,15 @@ class JoseHeader { * corresponding to the key used to digitally sign the JWS or encrypt the JWE. * @param x509SHA1Thumbprint the X.509 certificate SHA-1 thumbprint * @return the {@link AbstractBuilder} + * @deprecated The SHA-1 algorithm has been proven to be vulnerable to collision + * attacks and should not be used. See the Google + * Security Blog for more info. + * @see Announcing + * the first SHA1 collision */ + @Deprecated public B x509SHA1Thumbprint(String x509SHA1Thumbprint) { return header(JoseHeaderNames.X5T, x509SHA1Thumbprint); } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JoseHeaderNames.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JoseHeaderNames.java index a53318584f..b20863d6d5 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JoseHeaderNames.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JoseHeaderNames.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,7 +76,15 @@ public final class JoseHeaderNames { * {@code x5t} - the X.509 certificate SHA-1 thumbprint header is a base64url-encoded * SHA-1 thumbprint (a.k.a. digest) of the DER encoding of the X.509 certificate * corresponding to the key used to digitally sign a JWS or encrypt a JWE + * @deprecated The SHA-1 algorithm has been proven to be vulnerable to collision + * attacks and should not be used. See the Google + * Security Blog for more info. + * @see Announcing + * the first SHA1 collision */ + @Deprecated public static final String X5T = "x5t"; /** From 227a2cc0c72b06f718fd62b6d1a54a60c613dc9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 03:51:03 +0000 Subject: [PATCH 073/149] Bump com.webauthn4j:webauthn4j-core Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.29.2.RELEASE to 0.29.3.RELEASE. - [Release notes](https://github.com/webauthn4j/webauthn4j/releases) - [Changelog](https://github.com/webauthn4j/webauthn4j/blob/master/github-release-notes-generator.yml) - [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.29.2.RELEASE...0.29.3.RELEASE) --- updated-dependencies: - dependency-name: com.webauthn4j:webauthn4j-core dependency-version: 0.29.3.RELEASE dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6a16532177..efc1c81dbe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -108,7 +108,7 @@ org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-inf org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8.0.1969" org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1" -webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.29.2.RELEASE' +webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.29.3.RELEASE' [plugins] From ada75e76a6a9c435a842916bf1e8061c43215b67 Mon Sep 17 00:00:00 2001 From: Mark Bonnekessel <2949525+marbon87@users.noreply.github.com> Date: Fri, 16 May 2025 15:49:00 +0200 Subject: [PATCH 074/149] Add builder to create NimbusJwtDecoder with JwkSource Signed-off-by: Mark Bonnekessel <2949525+marbon87@users.noreply.github.com> --- .../security/oauth2/jwt/NimbusJwtDecoder.java | 112 ++++++++++++++++++ .../oauth2/jwt/NimbusJwtDecoderTests.java | 10 ++ 2 files changed, 122 insertions(+) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index 66f0b70e45..d762e919ac 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -261,6 +261,16 @@ public final class NimbusJwtDecoder implements JwtDecoder { return new SecretKeyJwtDecoderBuilder(secretKey); } + /** + * Use the given {@code JWKSource} to create a JwkSourceJwtDecoderBuilder. + * @param jwkSource the JWK Source to use + * @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations + * @since 7.0 + */ + public static JwkSourceJwtDecoderBuilder withJwkSource(JWKSource jwkSource) { + return new JwkSourceJwtDecoderBuilder(jwkSource); + } + /** * A builder for creating {@link NimbusJwtDecoder} instances based on a * JWK Set @@ -535,6 +545,108 @@ public final class NimbusJwtDecoder implements JwtDecoder { } + /** + * A builder for creating {@link NimbusJwtDecoder} instances based on a + * {@code JWKSource}. + */ + public static final class JwkSourceJwtDecoderBuilder { + + private static final JOSEObjectTypeVerifier NO_TYPE_VERIFIER = (header, context) -> { + }; + + private final Function, Set> defaultAlgorithms = (source) -> Set + .of(JWSAlgorithm.RS256); + + private final JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; + + private final Set signatureAlgorithms = new HashSet<>(); + + private Consumer> jwtProcessorCustomizer; + + private final JWKSource jwkSource; + + private JwkSourceJwtDecoderBuilder(JWKSource jwkSource) { + Assert.notNull(jwkSource, "jwkSource cannot be null"); + this.jwkSource = jwkSource; + this.jwtProcessorCustomizer = (processor) -> { + }; + } + + /** + * Append the given signing + * algorithm to the set of algorithms to use. + * @param signatureAlgorithm the algorithm to use + * @return a {@link JwkSourceJwtDecoderBuilder } for further configurations + */ + public JwkSourceJwtDecoderBuilder jwsAlgorithm(SignatureAlgorithm signatureAlgorithm) { + Assert.notNull(signatureAlgorithm, "signatureAlgorithm cannot be null"); + this.signatureAlgorithms.add(signatureAlgorithm); + return this; + } + + /** + * Configure the list of + * algorithms to use with the given {@link Consumer}. + * @param signatureAlgorithmsConsumer a {@link Consumer} for further configuring + * the algorithm list + * @return a {@link JwkSourceJwtDecoderBuilder } for further configurations + */ + public JwkSourceJwtDecoderBuilder jwsAlgorithms(Consumer> signatureAlgorithmsConsumer) { + Assert.notNull(signatureAlgorithmsConsumer, "signatureAlgorithmsConsumer cannot be null"); + signatureAlgorithmsConsumer.accept(this.signatureAlgorithms); + return this; + } + + /** + * Use the given {@link Consumer} to customize the {@link JWTProcessor + * ConfigurableJWTProcessor} before passing it to the build + * {@link NimbusJwtDecoder}. + * @param jwtProcessorCustomizer the callback used to alter the processor + * @return a {@link JwkSourceJwtDecoderBuilder } for further configurations + * @since 5.4 + */ + public JwkSourceJwtDecoderBuilder jwtProcessorCustomizer( + Consumer> jwtProcessorCustomizer) { + Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); + this.jwtProcessorCustomizer = jwtProcessorCustomizer; + return this; + } + + JWSKeySelector jwsKeySelector(JWKSource jwkSource) { + if (this.signatureAlgorithms.isEmpty()) { + return new JWSVerificationKeySelector<>(this.defaultAlgorithms.apply(jwkSource), jwkSource); + } + Set jwsAlgorithms = new HashSet<>(); + for (SignatureAlgorithm signatureAlgorithm : this.signatureAlgorithms) { + JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName()); + jwsAlgorithms.add(jwsAlgorithm); + } + return new JWSVerificationKeySelector<>(jwsAlgorithms, jwkSource); + } + + JWTProcessor processor() { + ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); + jwtProcessor.setJWSTypeVerifier(this.typeVerifier); + jwtProcessor.setJWSKeySelector(jwsKeySelector(this.jwkSource)); + // Spring Security validates the claim set independent from Nimbus + jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { + }); + this.jwtProcessorCustomizer.accept(jwtProcessor); + return jwtProcessor; + } + + /** + * Build the configured {@link NimbusJwtDecoder}. + * @return the configured {@link NimbusJwtDecoder} + */ + public NimbusJwtDecoder build() { + return new NimbusJwtDecoder(processor()); + } + + } + /** * A builder for creating {@link NimbusJwtDecoder} instances based on a public key. */ diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java index 91db639c45..7dd353d8f1 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java @@ -42,6 +42,7 @@ import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.crypto.MACSigner; import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.BadJOSEException; import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; @@ -557,6 +558,15 @@ public class NimbusJwtDecoderTests { // @formatter:on } + @Test + public void withJwkSourceWhenDefaultsThenUsesProvidedJwkSource() throws Exception { + JWKSource source = mock(JWKSource.class); + given(source.get(any(), any())).willReturn(JWKSet.parse(JWK_SET).getKeys()); + NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSource(source).build(); + Jwt jwt = decoder.decode(SIGNED_JWT); + assertThat(jwt.getClaimAsString("sub")).isEqualTo("test-subject"); + } + // gh-8730 @Test public void withSecretKeyWhenUsingCustomTypeHeaderThenSuccessfullyDecodes() throws Exception { From 195f9334382855e3af12aee6f7519dcf4cb725ef Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 28 May 2025 17:42:28 -0600 Subject: [PATCH 075/149] Allow Default Ordering for TargetVisitor In tests, we want to both test that functionality works and also demonstrate common or expected usage, where possible. It is likely incorrect to use @Order(0) for a target visitor as this states that it should take precedence over all Spring Security visitors defined at a lower precedence. Also, it appears this may have been added this way because of a mock visitor that appears to be unused by any tests. Further, when an application has multiple visitors, they should use the TargetVisitor.of method to publish one bean with the order determined by the order of the method parameters instead of having two separate beans. This commit removes the @Order(0) annotation and also the mock visitor, deferring to the natural ordering afforded by the framework. Issue gh-15994 --- .../PrePostMethodSecurityConfigurationTests.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 b8e46ffea0..404750ee95 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 @@ -40,7 +40,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.Mockito; import org.springframework.aop.Advisor; import org.springframework.aop.Pointcut; @@ -1831,8 +1830,8 @@ public class PrePostMethodSecurityConfigurationTests { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static TargetVisitor customTargetVisitor() { - return TargetVisitor.of(Mockito.mock(), TargetVisitor.defaultsSkipValueTypes()); + static TargetVisitor skipValueTypes() { + return TargetVisitor.defaultsSkipValueTypes(); } @Bean From d52e0b6a051d817c9af0c4ab8bc714b3233a9842 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:53:27 -0600 Subject: [PATCH 076/149] Polish NimbusJwtDecoder - Aligned JwkSourceJwtDecoderBuilder's relative position with its corresponding static factory - Added @since to JwkSourceJwtDecoderBuilder PR gh-17046 --- .../security/oauth2/jwt/NimbusJwtDecoder.java | 206 +++++++++--------- 1 file changed, 104 insertions(+), 102 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index d762e919ac..776f4e7335 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -545,108 +545,6 @@ public final class NimbusJwtDecoder implements JwtDecoder { } - /** - * A builder for creating {@link NimbusJwtDecoder} instances based on a - * {@code JWKSource}. - */ - public static final class JwkSourceJwtDecoderBuilder { - - private static final JOSEObjectTypeVerifier NO_TYPE_VERIFIER = (header, context) -> { - }; - - private final Function, Set> defaultAlgorithms = (source) -> Set - .of(JWSAlgorithm.RS256); - - private final JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; - - private final Set signatureAlgorithms = new HashSet<>(); - - private Consumer> jwtProcessorCustomizer; - - private final JWKSource jwkSource; - - private JwkSourceJwtDecoderBuilder(JWKSource jwkSource) { - Assert.notNull(jwkSource, "jwkSource cannot be null"); - this.jwkSource = jwkSource; - this.jwtProcessorCustomizer = (processor) -> { - }; - } - - /** - * Append the given signing - * algorithm to the set of algorithms to use. - * @param signatureAlgorithm the algorithm to use - * @return a {@link JwkSourceJwtDecoderBuilder } for further configurations - */ - public JwkSourceJwtDecoderBuilder jwsAlgorithm(SignatureAlgorithm signatureAlgorithm) { - Assert.notNull(signatureAlgorithm, "signatureAlgorithm cannot be null"); - this.signatureAlgorithms.add(signatureAlgorithm); - return this; - } - - /** - * Configure the list of - * algorithms to use with the given {@link Consumer}. - * @param signatureAlgorithmsConsumer a {@link Consumer} for further configuring - * the algorithm list - * @return a {@link JwkSourceJwtDecoderBuilder } for further configurations - */ - public JwkSourceJwtDecoderBuilder jwsAlgorithms(Consumer> signatureAlgorithmsConsumer) { - Assert.notNull(signatureAlgorithmsConsumer, "signatureAlgorithmsConsumer cannot be null"); - signatureAlgorithmsConsumer.accept(this.signatureAlgorithms); - return this; - } - - /** - * Use the given {@link Consumer} to customize the {@link JWTProcessor - * ConfigurableJWTProcessor} before passing it to the build - * {@link NimbusJwtDecoder}. - * @param jwtProcessorCustomizer the callback used to alter the processor - * @return a {@link JwkSourceJwtDecoderBuilder } for further configurations - * @since 5.4 - */ - public JwkSourceJwtDecoderBuilder jwtProcessorCustomizer( - Consumer> jwtProcessorCustomizer) { - Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); - this.jwtProcessorCustomizer = jwtProcessorCustomizer; - return this; - } - - JWSKeySelector jwsKeySelector(JWKSource jwkSource) { - if (this.signatureAlgorithms.isEmpty()) { - return new JWSVerificationKeySelector<>(this.defaultAlgorithms.apply(jwkSource), jwkSource); - } - Set jwsAlgorithms = new HashSet<>(); - for (SignatureAlgorithm signatureAlgorithm : this.signatureAlgorithms) { - JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName()); - jwsAlgorithms.add(jwsAlgorithm); - } - return new JWSVerificationKeySelector<>(jwsAlgorithms, jwkSource); - } - - JWTProcessor processor() { - ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); - jwtProcessor.setJWSTypeVerifier(this.typeVerifier); - jwtProcessor.setJWSKeySelector(jwsKeySelector(this.jwkSource)); - // Spring Security validates the claim set independent from Nimbus - jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { - }); - this.jwtProcessorCustomizer.accept(jwtProcessor); - return jwtProcessor; - } - - /** - * Build the configured {@link NimbusJwtDecoder}. - * @return the configured {@link NimbusJwtDecoder} - */ - public NimbusJwtDecoder build() { - return new NimbusJwtDecoder(processor()); - } - - } - /** * A builder for creating {@link NimbusJwtDecoder} instances based on a public key. */ @@ -903,4 +801,108 @@ public final class NimbusJwtDecoder implements JwtDecoder { } + /** + * A builder for creating {@link NimbusJwtDecoder} instances based on a + * {@code JWKSource}. + * + * @since 7.0 + */ + public static final class JwkSourceJwtDecoderBuilder { + + private static final JOSEObjectTypeVerifier NO_TYPE_VERIFIER = (header, context) -> { + }; + + private final Function, Set> defaultAlgorithms = (source) -> Set + .of(JWSAlgorithm.RS256); + + private final JOSEObjectTypeVerifier typeVerifier = NO_TYPE_VERIFIER; + + private final Set signatureAlgorithms = new HashSet<>(); + + private Consumer> jwtProcessorCustomizer; + + private final JWKSource jwkSource; + + private JwkSourceJwtDecoderBuilder(JWKSource jwkSource) { + Assert.notNull(jwkSource, "jwkSource cannot be null"); + this.jwkSource = jwkSource; + this.jwtProcessorCustomizer = (processor) -> { + }; + } + + /** + * Append the given signing + * algorithm to the set of algorithms to use. + * @param signatureAlgorithm the algorithm to use + * @return a {@link JwkSourceJwtDecoderBuilder } for further configurations + */ + public JwkSourceJwtDecoderBuilder jwsAlgorithm(SignatureAlgorithm signatureAlgorithm) { + Assert.notNull(signatureAlgorithm, "signatureAlgorithm cannot be null"); + this.signatureAlgorithms.add(signatureAlgorithm); + return this; + } + + /** + * Configure the list of + * algorithms to use with the given {@link Consumer}. + * @param signatureAlgorithmsConsumer a {@link Consumer} for further configuring + * the algorithm list + * @return a {@link JwkSourceJwtDecoderBuilder } for further configurations + */ + public JwkSourceJwtDecoderBuilder jwsAlgorithms(Consumer> signatureAlgorithmsConsumer) { + Assert.notNull(signatureAlgorithmsConsumer, "signatureAlgorithmsConsumer cannot be null"); + signatureAlgorithmsConsumer.accept(this.signatureAlgorithms); + return this; + } + + /** + * Use the given {@link Consumer} to customize the {@link JWTProcessor + * ConfigurableJWTProcessor} before passing it to the build + * {@link NimbusJwtDecoder}. + * @param jwtProcessorCustomizer the callback used to alter the processor + * @return a {@link JwkSourceJwtDecoderBuilder } for further configurations + * @since 5.4 + */ + public JwkSourceJwtDecoderBuilder jwtProcessorCustomizer( + Consumer> jwtProcessorCustomizer) { + Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); + this.jwtProcessorCustomizer = jwtProcessorCustomizer; + return this; + } + + JWSKeySelector jwsKeySelector(JWKSource jwkSource) { + if (this.signatureAlgorithms.isEmpty()) { + return new JWSVerificationKeySelector<>(this.defaultAlgorithms.apply(jwkSource), jwkSource); + } + Set jwsAlgorithms = new HashSet<>(); + for (SignatureAlgorithm signatureAlgorithm : this.signatureAlgorithms) { + JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName()); + jwsAlgorithms.add(jwsAlgorithm); + } + return new JWSVerificationKeySelector<>(jwsAlgorithms, jwkSource); + } + + JWTProcessor processor() { + ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); + jwtProcessor.setJWSTypeVerifier(this.typeVerifier); + jwtProcessor.setJWSKeySelector(jwsKeySelector(this.jwkSource)); + // Spring Security validates the claim set independent from Nimbus + jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { + }); + this.jwtProcessorCustomizer.accept(jwtProcessor); + return jwtProcessor; + } + + /** + * Build the configured {@link NimbusJwtDecoder}. + * @return the configured {@link NimbusJwtDecoder} + */ + public NimbusJwtDecoder build() { + return new NimbusJwtDecoder(processor()); + } + + } + } From 9654e51bd4b3b556e824c739a45bee3dfa4c3e76 Mon Sep 17 00:00:00 2001 From: dae won Date: Fri, 30 May 2025 12:13:39 +0900 Subject: [PATCH 077/149] Include UsernameNotFoundException in BadCredentialsException Closes gh-16496 Signed-off-by: dae won --- .../dao/AbstractUserDetailsAuthenticationProvider.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java index d107f9aa22..f0c5b2c50c 100644 --- a/core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java @@ -137,11 +137,12 @@ public abstract class AbstractUserDetailsAuthenticationProvider } catch (UsernameNotFoundException ex) { this.logger.debug(LogMessage.format("Failed to find user '%s'", username)); + String message = this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", + "Bad credentials"); if (!this.hideUserNotFoundExceptions) { throw ex; } - throw new BadCredentialsException(this.messages - .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); + throw new BadCredentialsException(message, ex); } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } From b20cfceabbd1fe9c019cfb724272f563ed667a63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 03:48:10 +0000 Subject: [PATCH 078/149] Bump io-spring-javaformat from 0.0.45 to 0.0.46 Bumps `io-spring-javaformat` from 0.0.45 to 0.0.46. Updates `io.spring.javaformat:spring-javaformat-checkstyle` from 0.0.45 to 0.0.46 - [Release notes](https://github.com/spring-io/spring-javaformat/releases) - [Commits](https://github.com/spring-io/spring-javaformat/compare/v0.0.45...v0.0.46) Updates `io.spring.javaformat:spring-javaformat-gradle-plugin` from 0.0.45 to 0.0.46 - [Release notes](https://github.com/spring-io/spring-javaformat/releases) - [Commits](https://github.com/spring-io/spring-javaformat/compare/v0.0.45...v0.0.46) --- updated-dependencies: - dependency-name: io.spring.javaformat:spring-javaformat-checkstyle dependency-version: 0.0.46 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.spring.javaformat:spring-javaformat-gradle-plugin dependency-version: 0.0.46 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6a16532177..f5379aff42 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] com-squareup-okhttp3 = "3.14.9" io-rsocket = "1.1.5" -io-spring-javaformat = "0.0.45" +io-spring-javaformat = "0.0.46" io-spring-nohttp = "0.0.11" jakarta-websocket = "2.2.0" org-apache-directory-server = "1.5.5" From 8953f464fb2d50dde2a8e783e919b0f3b158b1ee Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Wed, 14 May 2025 16:54:52 +0700 Subject: [PATCH 079/149] Add Switch for Processing GET Requests Closes gh-17099 Signed-off-by: Tran Ngoc Nhan --- .../Saml2AuthenticationTokenConverter.java | 23 +++++++++++++++---- ...aml2AuthenticationTokenConverterTests.java | 17 +++++++++++++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java index 8acc64bf97..73872bbe03 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,8 @@ public final class Saml2AuthenticationTokenConverter implements AuthenticationCo private Saml2AuthenticationRequestRepository authenticationRequestRepository; + private boolean shouldConvertGetRequests = true; + /** * Constructs a {@link Saml2AuthenticationTokenConverter} given a strategy for * resolving {@link RelyingPartyRegistration}s @@ -86,16 +88,27 @@ public final class Saml2AuthenticationTokenConverter implements AuthenticationCo this.authenticationRequestRepository = authenticationRequestRepository; } + /** + * Use the given {@code shouldConvertGetRequests} to convert {@code GET} requests. + * Default is {@code true}. + * @param shouldConvertGetRequests the {@code shouldConvertGetRequests} to use + * @since 7.0 + */ + public void setShouldConvertGetRequests(boolean shouldConvertGetRequests) { + this.shouldConvertGetRequests = shouldConvertGetRequests; + } + private String decode(HttpServletRequest request) { String encoded = request.getParameter(Saml2ParameterNames.SAML_RESPONSE); if (encoded == null) { return null; } + boolean isGet = HttpMethod.GET.matches(request.getMethod()); + if (!this.shouldConvertGetRequests && isGet) { + return null; + } try { - return Saml2Utils.withEncoded(encoded) - .requireBase64(true) - .inflate(HttpMethod.GET.matches(request.getMethod())) - .decode(); + return Saml2Utils.withEncoded(encoded).requireBase64(true).inflate(isGet).decode(); } catch (Exception ex) { throw new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, ex.getMessage()), diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java index f54788a4ea..b023d38cb6 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -230,6 +230,21 @@ public class Saml2AuthenticationTokenConverterTests { .isThrownBy(() -> converter.setAuthenticationRequestRepository(null)); } + @Test + public void shouldNotConvertGetRequests() { + Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter( + this.relyingPartyRegistrationResolver); + converter.setShouldConvertGetRequests(false); + given(this.relyingPartyRegistrationResolver.resolve(any(HttpServletRequest.class), any())) + .willReturn(this.relyingPartyRegistration); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("GET"); + request.setParameter(Saml2ParameterNames.SAML_RESPONSE, + Saml2Utils.samlEncode("response".getBytes(StandardCharsets.UTF_8))); + Saml2AuthenticationToken token = converter.convert(request); + assertThat(token).isNull(); + } + private void validateSsoCircleXml(String xml) { assertThat(xml).contains("InResponseTo=\"ARQ9a73ead-7dcf-45a8-89eb-26f3c9900c36\"") .contains(" ID=\"s246d157446618e90e43fb79bdd4d9e9e19cf2c7c4\"") From 4ed131f6ab89ec5082fb78a1cfcc75258451e681 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 3 Jun 2025 12:19:56 -0600 Subject: [PATCH 080/149] Add shouldConvertGetRequests Migration Steps Issue gh-17099 --- .../ROOT/pages/migration/servlet/oauth2.adoc | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc index 3caeec4133..e80e0a916e 100644 --- a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc @@ -78,3 +78,42 @@ fun jwtDecoder(): JwtDecoder { ====== <1> - `validateTypes` now defaults to `false` <2> - `JwtTypeValidator#jwt` is added by all `createDefaultXXX` methods + +== Do Not Process `` GET Requests with `Saml2AuthenticationTokenConverter` + +Spring Security does not support processing `` payloads over GET as this is not supported by the SAML 2.0 spec. + +To better comply with this, `Saml2AuthenticationTokenConverter` will not process GET requests by default as of Spring Security 8. +To prepare for this, the property `shouldConvertGetRequests` is available. +To use it, publish your own `Saml2AuthenticationTokenConverter` like so: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +Saml2AuthenticationTokenConverter authenticationConverter(RelyingPartyRegistrationRepository registrations) { + Saml2AuhenticationTokenConverter authenticationConverter = new Saml2AuthenticationTokenConverter( + new DefaultRelyingPartyRegistrationResolver(registrations)); + authenticationConverter.setShouldConvertGetRequests(false); + return authenticationConverter; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun authenticationConverter(val registrations: RelyingPartyRegistrationRepository): Saml2AuthenticationTokenConverter { + val authenticationConverter = new Saml2AuthenticationTokenConverter( + DefaultRelyingPartyRegistrationResolver(registrations)) + authenticationConverter.setShouldConvertGetRequests(false) + return authenticationConverter +} +---- +====== + +If you must continue using `Saml2AuthenticationTokenConverter` to process GET requests, you can call `setShouldConvertGetRequests` to `true.` From 3de7312658b7f76d88b346d077f700ab654b7fff Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 3 Jun 2025 12:56:45 -0600 Subject: [PATCH 081/149] Add Saml2Error Static Factories --- .../security/saml2/core/Saml2Error.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2Error.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2Error.java index 6709092ced..4e6653b9b5 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2Error.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2Error.java @@ -51,6 +51,66 @@ public class Saml2Error implements Serializable { this.description = description; } + /** + * Construct an {@link Saml2ErrorCodes#INVALID_RESPONSE} error + * @param description the error description + * @return the resulting {@link Saml2Error} + * @since 7.0 + */ + public static Saml2Error invalidResponse(String description) { + return new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, description); + } + + /** + * Construct an {@link Saml2ErrorCodes#INTERNAL_VALIDATION_ERROR} error + * @param description the error description + * @return the resulting {@link Saml2Error} + * @since 7.0 + */ + public static Saml2Error internalValidationError(String description) { + return new Saml2Error(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, description); + } + + /** + * Construct an {@link Saml2ErrorCodes#MALFORMED_RESPONSE_DATA} error + * @param description the error description + * @return the resulting {@link Saml2Error} + * @since 7.0 + */ + public static Saml2Error malformedResponseData(String description) { + return new Saml2Error(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, description); + } + + /** + * Construct an {@link Saml2ErrorCodes#DECRYPTION_ERROR} error + * @param description the error description + * @return the resulting {@link Saml2Error} + * @since 7.0 + */ + public static Saml2Error decryptionError(String description) { + return new Saml2Error(Saml2ErrorCodes.DECRYPTION_ERROR, description); + } + + /** + * Construct an {@link Saml2ErrorCodes#RELYING_PARTY_REGISTRATION_NOT_FOUND} error + * @param description the error description + * @return the resulting {@link Saml2Error} + * @since 7.0 + */ + public static Saml2Error relyingPartyRegistrationNotFound(String description) { + return new Saml2Error(Saml2ErrorCodes.RELYING_PARTY_REGISTRATION_NOT_FOUND, description); + } + + /** + * Construct an {@link Saml2ErrorCodes#SUBJECT_NOT_FOUND} error + * @param description the error description + * @return the resulting {@link Saml2Error} + * @since 7.0 + */ + public static Saml2Error subjectNotFound(String description) { + return new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, description); + } + /** * Returns the error code. * @return the error code From 32c7e8a6eecdb6a55da15c3d06f102b07d81ce86 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 3 Jun 2025 12:56:56 -0600 Subject: [PATCH 082/149] Use Saml2Error Static Factories --- .../BaseOpenSamlAuthenticationProvider.java | 15 +++++---------- .../BaseOpenSamlAuthenticationTokenConverter.java | 4 +--- .../web/Saml2AuthenticationTokenConverter.java | 7 +++---- ...lLogoutRequestValidatorParametersResolver.java | 4 +--- .../logout/Saml2LogoutRequestFilter.java | 4 +--- .../web/OpenSamlAuthenticationTokenConverter.java | 4 +--- ...lLogoutRequestValidatorParametersResolver.java | 4 +--- .../OpenSaml5AuthenticationProvider.java | 4 ++-- 8 files changed, 15 insertions(+), 31 deletions(-) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/BaseOpenSamlAuthenticationProvider.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/BaseOpenSamlAuthenticationProvider.java index 32b4fa61ec..b02f82062d 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/BaseOpenSamlAuthenticationProvider.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/BaseOpenSamlAuthenticationProvider.java @@ -302,7 +302,7 @@ class BaseOpenSamlAuthenticationProvider implements AuthenticationProvider { throw ex; } catch (Exception ex) { - throw createAuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, ex.getMessage(), ex); + throw new Saml2AuthenticationException(Saml2Error.internalValidationError(ex.getMessage()), ex); } } @@ -316,7 +316,7 @@ class BaseOpenSamlAuthenticationProvider implements AuthenticationProvider { return this.saml.deserialize(response); } catch (Exception ex) { - throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, ex.getMessage(), ex); + throw new Saml2AuthenticationException(Saml2Error.malformedResponseData(ex.getMessage()), ex); } } @@ -375,7 +375,7 @@ class BaseOpenSamlAuthenticationProvider implements AuthenticationProvider { .debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() + "]"); } Saml2Error first = errors.iterator().next(); - throw createAuthenticationException(first.getErrorCode(), first.getDescription(), null); + throw new Saml2AuthenticationException(first); } else { if (this.logger.isDebugEnabled()) { @@ -408,7 +408,7 @@ class BaseOpenSamlAuthenticationProvider implements AuthenticationProvider { this.saml.withDecryptionKeys(registration.getDecryptionX509Credentials()).decrypt(response); } catch (Exception ex) { - throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); + throw new Saml2AuthenticationException(Saml2Error.decryptionError(ex.getMessage()), ex); } }; } @@ -437,7 +437,7 @@ class BaseOpenSamlAuthenticationProvider implements AuthenticationProvider { this.saml.withDecryptionKeys(registration.getDecryptionX509Credentials()).decrypt(assertion); } catch (Exception ex) { - throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); + throw new Saml2AuthenticationException(Saml2Error.decryptionError(ex.getMessage()), ex); } }; } @@ -503,11 +503,6 @@ class BaseOpenSamlAuthenticationProvider implements AuthenticationProvider { return xmlObject; } - private static Saml2AuthenticationException createAuthenticationException(String code, String message, - Exception cause) { - return new Saml2AuthenticationException(new Saml2Error(code, message), cause); - } - private static Converter createAssertionValidator(String errorCode, Converter validatorConverter, Converter contextConverter) { diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/BaseOpenSamlAuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/BaseOpenSamlAuthenticationTokenConverter.java index a88847aeb6..1643959155 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/BaseOpenSamlAuthenticationTokenConverter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/BaseOpenSamlAuthenticationTokenConverter.java @@ -22,7 +22,6 @@ import org.opensaml.saml.saml2.core.Response; import org.springframework.http.HttpMethod; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; @@ -182,8 +181,7 @@ final class BaseOpenSamlAuthenticationTokenConverter implements AuthenticationCo .decode(); } catch (Exception ex) { - throw new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, ex.getMessage()), - ex); + throw new Saml2AuthenticationException(Saml2Error.invalidResponse(ex.getMessage()), ex); } } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java index 73872bbe03..9a43edce6e 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java @@ -20,7 +20,6 @@ import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.HttpMethod; import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; @@ -107,12 +106,12 @@ public final class Saml2AuthenticationTokenConverter implements AuthenticationCo if (!this.shouldConvertGetRequests && isGet) { return null; } + Saml2Utils.DecodingConfigurer decoding = Saml2Utils.withEncoded(encoded).requireBase64(true).inflate(isGet); try { - return Saml2Utils.withEncoded(encoded).requireBase64(true).inflate(isGet).decode(); + return decoding.decode(); } catch (Exception ex) { - throw new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, ex.getMessage()), - ex); + throw new Saml2AuthenticationException(Saml2Error.invalidResponse(ex.getMessage()), ex); } } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java index 2e58340aef..84a53005aa 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java @@ -23,7 +23,6 @@ import org.springframework.http.HttpMethod; import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; @@ -145,8 +144,7 @@ final class BaseOpenSamlLogoutRequestValidatorParametersResolver RelyingPartyRegistration registration = this.registrations.findByRegistrationId(registrationId); if (registration == null) { throw new Saml2AuthenticationException( - new Saml2Error(Saml2ErrorCodes.RELYING_PARTY_REGISTRATION_NOT_FOUND, "registration not found"), - "registration not found"); + Saml2Error.relyingPartyRegistrationNotFound("registration not found")); } return logoutRequestByRegistration(request, registration, authentication); } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java index fd68bbc3a9..1476f37624 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java @@ -31,7 +31,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; @@ -268,8 +267,7 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter { registrationId); if (registration == null) { throw new Saml2AuthenticationException( - new Saml2Error(Saml2ErrorCodes.RELYING_PARTY_REGISTRATION_NOT_FOUND, "registration not found"), - "registration not found"); + Saml2Error.relyingPartyRegistrationNotFound("registration not found")); } UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration); String entityId = uriResolver.resolve(registration.getEntityId()); diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSamlAuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSamlAuthenticationTokenConverter.java index 16f8908fd9..c680f93bb3 100644 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSamlAuthenticationTokenConverter.java +++ b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSamlAuthenticationTokenConverter.java @@ -24,7 +24,6 @@ import org.opensaml.saml.saml2.core.Response; import org.springframework.http.HttpMethod; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; @@ -197,8 +196,7 @@ public final class OpenSamlAuthenticationTokenConverter implements Authenticatio .decode(); } catch (Exception ex) { - throw new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, ex.getMessage()), - ex); + throw new Saml2AuthenticationException(Saml2Error.invalidResponse(ex.getMessage()), ex); } } diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java index 27a610a896..aae95e28c7 100644 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java +++ b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java @@ -27,7 +27,6 @@ import org.springframework.http.HttpMethod; import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; @@ -159,8 +158,7 @@ public final class OpenSamlLogoutRequestValidatorParametersResolver RelyingPartyRegistration registration = this.registrations.findByRegistrationId(registrationId); if (registration == null) { throw new Saml2AuthenticationException( - new Saml2Error(Saml2ErrorCodes.RELYING_PARTY_REGISTRATION_NOT_FOUND, "registration not found"), - "registration not found"); + Saml2Error.relyingPartyRegistrationNotFound("registration not found")); } return logoutRequestByRegistration(request, registration, authentication); } diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java index e3cde7974e..d2dbb5ba0c 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java @@ -935,8 +935,8 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv private static String authenticatedPrincipal(Assertion assertion) { if (!BaseOpenSamlAuthenticationProvider.hasName(assertion)) { - throw new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, - "Assertion [" + assertion.getID() + "] is missing a subject")); + throw new Saml2AuthenticationException( + Saml2Error.subjectNotFound("Assertion [" + assertion.getID() + "] is missing a subject")); } return assertion.getSubject().getNameID().getValue(); } From f73f253beb4bddbbabe1abe9bc58737a3ca82b6f Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 3 Jun 2025 13:03:21 -0600 Subject: [PATCH 083/149] Add Switch for Processing GET Requests Issue gh-17099 --- ...BaseOpenSamlAuthenticationTokenConverter.java | 16 ++++++++++++---- .../OpenSaml4AuthenticationTokenConverter.java | 10 ++++++++++ .../OpenSaml5AuthenticationTokenConverter.java | 10 ++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/BaseOpenSamlAuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/BaseOpenSamlAuthenticationTokenConverter.java index 1643959155..e47ff4f48f 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/BaseOpenSamlAuthenticationTokenConverter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/BaseOpenSamlAuthenticationTokenConverter.java @@ -51,6 +51,8 @@ final class BaseOpenSamlAuthenticationTokenConverter implements AuthenticationCo private Saml2AuthenticationRequestRepository authenticationRequests = new HttpSessionSaml2AuthenticationRequestRepository(); + private boolean shouldConvertGetRequests = true; + /** * Constructs a {@link BaseOpenSamlAuthenticationTokenConverter} given a repository * for {@link RelyingPartyRegistration}s @@ -172,13 +174,19 @@ final class BaseOpenSamlAuthenticationTokenConverter implements AuthenticationCo this.requestMatcher = requestMatcher; } + void setShouldConvertGetRequests(boolean shouldConvertGetRequests) { + this.shouldConvertGetRequests = shouldConvertGetRequests; + } + private String decode(HttpServletRequest request) { String encoded = request.getParameter(Saml2ParameterNames.SAML_RESPONSE); + boolean isGet = HttpMethod.GET.matches(request.getMethod()); + if (!this.shouldConvertGetRequests && isGet) { + return null; + } + Saml2Utils.DecodingConfigurer decoding = Saml2Utils.withEncoded(encoded).requireBase64(true).inflate(isGet); try { - return Saml2Utils.withEncoded(encoded) - .requireBase64(true) - .inflate(HttpMethod.GET.matches(request.getMethod())) - .decode(); + return decoding.decode(); } catch (Exception ex) { throw new Saml2AuthenticationException(Saml2Error.invalidResponse(ex.getMessage()), ex); diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml4AuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml4AuthenticationTokenConverter.java index 70186f03d0..e82ecea767 100644 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml4AuthenticationTokenConverter.java +++ b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml4AuthenticationTokenConverter.java @@ -101,4 +101,14 @@ public final class OpenSaml4AuthenticationTokenConverter implements Authenticati this.delegate.setRequestMatcher(requestMatcher); } + /** + * Use the given {@code shouldConvertGetRequests} to convert {@code GET} requests. + * Default is {@code true}. + * @param shouldConvertGetRequests the {@code shouldConvertGetRequests} to use + * @since 7.0 + */ + public void setShouldConvertGetRequests(boolean shouldConvertGetRequests) { + this.delegate.setShouldConvertGetRequests(shouldConvertGetRequests); + } + } diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5AuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5AuthenticationTokenConverter.java index 48a2bd35a2..570718b7cf 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5AuthenticationTokenConverter.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5AuthenticationTokenConverter.java @@ -101,4 +101,14 @@ public final class OpenSaml5AuthenticationTokenConverter implements Authenticati this.delegate.setRequestMatcher(requestMatcher); } + /** + * Use the given {@code shouldConvertGetRequests} to convert {@code GET} requests. + * Default is {@code true}. + * @param shouldConvertGetRequests the {@code shouldConvertGetRequests} to use + * @since 7.0 + */ + public void setShouldConvertGetRequests(boolean shouldConvertGetRequests) { + this.delegate.setShouldConvertGetRequests(shouldConvertGetRequests); + } + } From 492444c5882c118eb8d8a538d8a628521cd58b2d Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 3 Jun 2025 13:08:51 -0600 Subject: [PATCH 084/149] Update shouldConvertGetRequests Migration Steps Issue gh-17099 --- .../ROOT/pages/migration/servlet/oauth2.adoc | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc index e80e0a916e..6cdb9043dd 100644 --- a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc @@ -83,9 +83,9 @@ fun jwtDecoder(): JwtDecoder { Spring Security does not support processing `` payloads over GET as this is not supported by the SAML 2.0 spec. -To better comply with this, `Saml2AuthenticationTokenConverter` will not process GET requests by default as of Spring Security 8. +To better comply with this, `Saml2AuthenticationTokenConverter`, `OpenSaml4AuthenticationTokenConverter`, and `OpenSaml5AuthenticationTokenConverter` will not process GET requests by default as of Spring Security 8. To prepare for this, the property `shouldConvertGetRequests` is available. -To use it, publish your own `Saml2AuthenticationTokenConverter` like so: +To use it, publish your own converter like so: [tabs] ====== @@ -94,9 +94,8 @@ Java:: [source,java,role="primary"] ---- @Bean -Saml2AuthenticationTokenConverter authenticationConverter(RelyingPartyRegistrationRepository registrations) { - Saml2AuhenticationTokenConverter authenticationConverter = new Saml2AuthenticationTokenConverter( - new DefaultRelyingPartyRegistrationResolver(registrations)); +OpenSaml5AuthenticationTokenConverter authenticationConverter(RelyingPartyRegistrationRepository registrations) { + OpenSaml5AuthenticationTokenConverter authenticationConverter = new OpenSaml5AuthenticationTokenConverter(registrations); authenticationConverter.setShouldConvertGetRequests(false); return authenticationConverter; } @@ -108,12 +107,11 @@ Kotlin:: ---- @Bean fun authenticationConverter(val registrations: RelyingPartyRegistrationRepository): Saml2AuthenticationTokenConverter { - val authenticationConverter = new Saml2AuthenticationTokenConverter( - DefaultRelyingPartyRegistrationResolver(registrations)) + val authenticationConverter = Saml2AuthenticationTokenConverter(registrations) authenticationConverter.setShouldConvertGetRequests(false) return authenticationConverter } ---- ====== -If you must continue using `Saml2AuthenticationTokenConverter` to process GET requests, you can call `setShouldConvertGetRequests` to `true.` +If you must continue using `Saml2AuthenticationTokenConverter`, `OpenSaml4AuthenticationTokenConverter`, or `OpenSaml5AuthenticationTokenConverter` to process GET requests, you can call `setShouldConvertGetRequests` to `true.` From 0ec7a7a30562dc86f117faaadaf88b0cce81ca9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 03:47:25 +0000 Subject: [PATCH 085/149] Bump io.spring.develocity.conventions from 0.0.22 to 0.0.23 Bumps [io.spring.develocity.conventions](https://github.com/spring-io/develocity-conventions) from 0.0.22 to 0.0.23. - [Release notes](https://github.com/spring-io/develocity-conventions/releases) - [Commits](https://github.com/spring-io/develocity-conventions/compare/v0.0.22...v0.0.23) --- updated-dependencies: - dependency-name: io.spring.develocity.conventions dependency-version: 0.0.23 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index 74cb46b16b..a1af657b56 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,7 +5,7 @@ pluginManagement { } plugins { - id "io.spring.develocity.conventions" version "0.0.22" + id "io.spring.develocity.conventions" version "0.0.23" } dependencyResolutionManagement { From 4967f3feee0b0f5906132eb3467fff3860688463 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Fri, 11 Apr 2025 15:26:00 +0300 Subject: [PATCH 086/149] Add Support BearerTokenAuthenticationConverter Closes gh-14750 Signed-off-by: Max Batischev --- .../OAuth2ResourceServerConfigurer.java | 97 ++++++++-- ...th2ResourceServerBeanDefinitionParser.java | 76 +++++++- .../security/config/spring-security-6.5.rnc | 3 + .../security/config/spring-security-6.5.xsd | 6 + .../OAuth2ResourceServerConfigurerTests.java | 83 +++++++- ...sourceServerBeanDefinitionParserTests.java | 41 ++-- ...ionParserTests-AuthenticationConverter.xml | 32 ++++ ...icationConverterAndBearerTokenResolver.xml | 32 ++++ ...arserTests-MockAuthenticationConverter.xml | 27 +++ .../servlet/appendix/namespace/http.adoc | 4 + .../resource/web/BearerTokenResolver.java | 5 +- .../BearerTokenAuthenticationConverter.java | 181 ++++++++++++++++++ .../BearerTokenAuthenticationFilter.java | 35 +++- ...arerTokenAuthenticationConverterTests.java | 148 ++++++++++++++ .../BearerTokenAuthenticationFilterTests.java | 10 + 15 files changed, 722 insertions(+), 58 deletions(-) create mode 100644 config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationConverter.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationConverterAndBearerTokenResolver.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockAuthenticationConverter.xml create mode 100644 oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java create mode 100644 oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index e9a425d46d..8d44dfdcb5 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.function.Supplier; import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; import org.springframework.core.convert.converter.Converter; @@ -37,10 +39,12 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; @@ -49,13 +53,14 @@ import org.springframework.security.oauth2.server.resource.introspection.OpaqueT import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; -import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; +import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter; import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.access.DelegatingAccessDeniedHandler; +import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.csrf.CsrfException; import org.springframework.security.web.util.matcher.AndRequestMatcher; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; @@ -64,6 +69,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.accept.HeaderContentNegotiationStrategy; @@ -156,7 +162,7 @@ public final class OAuth2ResourceServerConfigurer authenticationManagerResolver; - private BearerTokenResolver bearerTokenResolver; + private AuthenticationConverter authenticationConverter; private JwtConfigurer jwtConfigurer; @@ -194,9 +200,25 @@ public final class OAuth2ResourceServerConfigurer bearerTokenResolver(BearerTokenResolver bearerTokenResolver) { Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); - this.bearerTokenResolver = bearerTokenResolver; + this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver); + return this; + } + + /** + * Sets the {@link AuthenticationConverter} to use. + * @param authenticationConverter the authentication converter + * @return the {@link OAuth2ResourceServerConfigurer} for further configuration + * @since 6.5 + */ + public OAuth2ResourceServerConfigurer authenticationConverter(AuthenticationConverter authenticationConverter) { + Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); + this.authenticationConverter = authenticationConverter; return this; } @@ -271,8 +293,6 @@ public final class OAuth2ResourceServerConfigurer 0) { - this.bearerTokenResolver = this.context.getBean(BearerTokenResolver.class); - } - else { - this.bearerTokenResolver = new DefaultBearerTokenResolver(); - } + AuthenticationConverter getAuthenticationConverter() { + if (this.authenticationConverter != null) { + return this.authenticationConverter; } - return this.bearerTokenResolver; + if (this.context.getBeanNamesForType(AuthenticationConverter.class).length > 0) { + this.authenticationConverter = this.context.getBean(AuthenticationConverter.class); + } + else if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) { + BearerTokenResolver bearerTokenResolver = this.context.getBean(BearerTokenResolver.class); + this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver); + } + else { + this.authenticationConverter = new BearerTokenAuthenticationConverter(); + } + return this.authenticationConverter; + } + + BearerTokenResolver getBearerTokenResolver() { + AuthenticationConverter authenticationConverter = getAuthenticationConverter(); + if (authenticationConverter instanceof BearerTokenResolverAuthenticationConverterAdapter bearer) { + return bearer.bearerTokenResolver; + } + return null; } public class JwtConfigurer { @@ -560,21 +595,43 @@ public final class OAuth2ResourceServerConfigurer + + + Reference to a AuthenticationConverter + + + diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index 6b263c7048..5c655f5afd 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -127,12 +127,14 @@ import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthen import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; +import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter; import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; +import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; @@ -759,13 +761,6 @@ public class OAuth2ResourceServerConfigurerTests { assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver); } - @Test - public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() { - ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext(); - OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); - assertThat(oauth2.getBearerTokenResolver()).isInstanceOf(DefaultBearerTokenResolver.class); - } - @Test public void requestWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception { this.spring.register(CustomAuthenticationDetailsSource.class, JwtDecoderConfig.class, BasicController.class) @@ -1415,6 +1410,47 @@ public class OAuth2ResourceServerConfigurerTests { verify(authenticationConverter).convert(any(), any()); } + @Test + public void getAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() { + AuthenticationConverter converter = mock(AuthenticationConverter.class); + AuthenticationConverter converterBean = mock(AuthenticationConverter.class); + GenericWebApplicationContext context = new GenericWebApplicationContext(); + context.registerBean("converterOne", AuthenticationConverter.class, () -> converterBean); + context.registerBean("converterTwo", AuthenticationConverter.class, () -> converterBean); + this.spring.context(context).autowire(); + OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); + oauth2.authenticationConverter(converter); + assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter); + } + + @Test + public void getAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() { + AuthenticationConverter converter = mock(AuthenticationConverter.class); + AuthenticationConverter converterBean = mock(AuthenticationConverter.class); + GenericWebApplicationContext context = new GenericWebApplicationContext(); + context.registerBean(AuthenticationConverter.class, () -> converterBean); + this.spring.context(context).autowire(); + OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); + oauth2.authenticationConverter(converter); + assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter); + } + + @Test + public void getAuthenticationConverterWhenDuplicateConverterBeansThenWiringException() { + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy( + () -> this.spring.register(MultipleAuthenticationConverterBeansConfig.class, JwtDecoderConfig.class) + .autowire()) + .withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class); + } + + @Test + public void getAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() { + ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext(); + OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); + assertThat(oauth2.getAuthenticationConverter()).isInstanceOf(BearerTokenAuthenticationConverter.class); + } + private static void registerMockBean(GenericApplicationContext context, String name, Class clazz) { context.registerBean(name, clazz, () -> mock(clazz)); } @@ -2516,6 +2552,39 @@ public class OAuth2ResourceServerConfigurerTests { } + @Configuration + @EnableWebSecurity + static class MultipleAuthenticationConverterBeansConfig { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .oauth2ResourceServer() + .jwt(); + return http.build(); + // @formatter:on + } + + @Bean + AuthenticationConverter authenticationConverterOne() { + BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter(); + converter.setAllowUriQueryParameter(true); + return converter; + } + + @Bean + AuthenticationConverter authenticationConverterTwo() { + BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter(); + converter.setAllowUriQueryParameter(true); + return converter; + } + + } + @Configuration @EnableWebSecurity static class MultipleIssuersConfig { diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java index 6a01051ca4..5ad167eef8 100644 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import java.time.Instant; import java.time.ZoneId; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; @@ -50,13 +49,11 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.w3c.dom.Element; -import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.XmlReaderContext; @@ -85,12 +82,14 @@ import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.TestJwts; +import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; +import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -462,6 +461,24 @@ public class OAuth2ResourceServerBeanDefinitionParserTests { verify(bearerTokenResolver).resolve(any(HttpServletRequest.class)); } + @Test + public void getWhenCustomAuthenticationConverterThenUses() throws Exception { + this.spring + .configLocations(xml("MockAuthenticationConverter"), xml("MockJwtDecoder"), xml("AuthenticationConverter")) + .autowire(); + JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); + given(decoder.decode("token")).willReturn(TestJwts.jwt().build()); + AuthenticationConverter authenticationConverter = this.spring.getContext() + .getBean(AuthenticationConverter.class); + given(authenticationConverter.convert(any(HttpServletRequest.class))) + .willReturn(new BearerTokenAuthenticationToken("token")); + + this.mvc.perform(get("/")).andExpect(status().isNotFound()); + + verify(decoder).decode("token"); + verify(authenticationConverter).convert(any(HttpServletRequest.class)); + } + @Test public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted() throws Exception { @@ -521,14 +538,6 @@ public class OAuth2ResourceServerBeanDefinitionParserTests { // @formatter:on } - @Test - public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() { - OAuth2ResourceServerBeanDefinitionParser oauth2 = new OAuth2ResourceServerBeanDefinitionParser( - mock(BeanReference.class), mock(List.class), mock(Map.class), mock(Map.class), mock(List.class), - mock(BeanMetadataElement.class)); - assertThat(oauth2.getBearerTokenResolver(mock(Element.class))).isInstanceOf(RootBeanDefinition.class); - } - @Test public void requestWhenCustomJwtDecoderThenUsed() throws Exception { this.spring.configLocations(xml("MockJwtDecoder"), xml("Jwt")).autowire(); @@ -545,6 +554,12 @@ public class OAuth2ResourceServerBeanDefinitionParserTests { .isThrownBy(() -> this.spring.configLocations(xml("JwtDecoderAndJwkSetUri")).autowire()); } + @Test + public void configureWhenAuthenticationConverterAndJwkSetUriThenException() { + assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy( + () -> this.spring.configLocations(xml("AuthenticationConverterAndBearerTokenResolver")).autowire()); + } + @Test public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() throws Exception { this.spring.configLocations(xml("MockJwtDecoder"), xml("AuthenticationEntryPoint")).autowire(); diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationConverter.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationConverter.xml new file mode 100644 index 0000000000..04d3932f09 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationConverter.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationConverterAndBearerTokenResolver.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationConverterAndBearerTokenResolver.xml new file mode 100644 index 0000000000..c0cb49bf65 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationConverterAndBearerTokenResolver.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockAuthenticationConverter.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockAuthenticationConverter.xml new file mode 100644 index 0000000000..397c4c59bf --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-MockAuthenticationConverter.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index 2b434d4303..d5438e0f43 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -1272,6 +1272,10 @@ Reference to a `BearerTokenResolver` which will retrieve the bearer token from t * **entry-point-ref** Reference to a `AuthenticationEntryPoint` which will handle unauthorized requests +[[nsa-oauth2-resource-server-authentication-converter-ref]] +* **authentication-converter-ref** +Reference to a `AuthenticationConverter` which convert request to authentication + [[nsa-jwt]] == Represents an OAuth 2.0 Resource Server that will authorize JWTs diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java index 7abd174630..0fd023c5f6 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,10 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; * @since 5.1 * @see RFC 6750 * Section 2: Authenticated Requests + * @deprecated Use + * {@link org.springframework.security.web.authentication.AuthenticationConverter} instead */ +@Deprecated @FunctionalInterface public interface BearerTokenResolver { diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java new file mode 100644 index 0000000000..211a49bf21 --- /dev/null +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java @@ -0,0 +1,181 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.server.resource.web.authentication; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jakarta.servlet.http.HttpServletRequest; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.server.resource.BearerTokenError; +import org.springframework.security.oauth2.server.resource.BearerTokenErrors; +import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; +import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Implementation of {@link AuthenticationConverter}, that converts request to + * {@link BearerTokenAuthenticationToken} + * + * @author Max Batischev + * @since 6.5 + */ +public final class BearerTokenAuthenticationConverter implements AuthenticationConverter { + + private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); + + private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?[a-zA-Z0-9-._~+/]+=*)$", + Pattern.CASE_INSENSITIVE); + + private static final String ACCESS_TOKEN_PARAMETER_NAME = "access_token"; + + private boolean allowFormEncodedBodyParameter = false; + + private boolean allowUriQueryParameter = false; + + private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION; + + @Override + public Authentication convert(HttpServletRequest request) { + String token = resolveToken(request); + if (StringUtils.hasText(token)) { + BearerTokenAuthenticationToken authenticationToken = new BearerTokenAuthenticationToken(token); + authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); + + return authenticationToken; + } + return null; + } + + private String resolveToken(HttpServletRequest request) { + final String authorizationHeaderToken = resolveFromAuthorizationHeader(request); + final String parameterToken = isParameterTokenSupportedForRequest(request) + ? resolveFromRequestParameters(request) : null; + if (authorizationHeaderToken != null) { + if (parameterToken != null) { + final BearerTokenError error = BearerTokenErrors + .invalidRequest("Found multiple bearer tokens in the request"); + throw new OAuth2AuthenticationException(error); + } + return authorizationHeaderToken; + } + if (parameterToken != null && isParameterTokenEnabledForRequest(request)) { + return parameterToken; + } + return null; + } + + private String resolveFromAuthorizationHeader(HttpServletRequest request) { + String authorization = request.getHeader(this.bearerTokenHeaderName); + if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) { + return null; + } + Matcher matcher = authorizationPattern.matcher(authorization); + if (!matcher.matches()) { + BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed"); + throw new OAuth2AuthenticationException(error); + } + return matcher.group("token"); + } + + private boolean isParameterTokenEnabledForRequest(HttpServletRequest request) { + return ((this.allowFormEncodedBodyParameter && isFormEncodedRequest(request) && !isGetRequest(request) + && !hasAccessTokenInQueryString(request)) || (this.allowUriQueryParameter && isGetRequest(request))); + } + + private static String resolveFromRequestParameters(HttpServletRequest request) { + String[] values = request.getParameterValues(ACCESS_TOKEN_PARAMETER_NAME); + if (values == null || values.length == 0) { + return null; + } + if (values.length == 1) { + return values[0]; + } + BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request"); + throw new OAuth2AuthenticationException(error); + } + + private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) { + return isFormEncodedRequest(request) || isGetRequest(request); + } + + private boolean isGetRequest(HttpServletRequest request) { + return HttpMethod.GET.name().equals(request.getMethod()); + } + + private boolean isFormEncodedRequest(HttpServletRequest request) { + return MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()); + } + + private static boolean hasAccessTokenInQueryString(HttpServletRequest request) { + return (request.getQueryString() != null) && request.getQueryString().contains(ACCESS_TOKEN_PARAMETER_NAME); + } + + /** + * Set if transport of access token using URI query parameter is supported. Defaults + * to {@code false}. + * + * The spec recommends against using this mechanism for sending bearer tokens, and + * even goes as far as stating that it was only included for completeness. + * @param allowUriQueryParameter if the URI query parameter is supported + */ + public void setAllowUriQueryParameter(boolean allowUriQueryParameter) { + this.allowUriQueryParameter = allowUriQueryParameter; + } + + /** + * Set this value to configure what header is checked when resolving a Bearer Token. + * This value is defaulted to {@link HttpHeaders#AUTHORIZATION}. + * + * This allows other headers to be used as the Bearer Token source such as + * {@link HttpHeaders#PROXY_AUTHORIZATION} + * @param bearerTokenHeaderName the header to check when retrieving the Bearer Token. + */ + public void setBearerTokenHeaderName(String bearerTokenHeaderName) { + this.bearerTokenHeaderName = bearerTokenHeaderName; + } + + /** + * Set if transport of access token using form-encoded body parameter is supported. + * Defaults to {@code false}. + * @param allowFormEncodedBodyParameter if the form-encoded body parameter is + * supported + */ + public void setAllowFormEncodedBodyParameter(boolean allowFormEncodedBodyParameter) { + this.allowFormEncodedBodyParameter = allowFormEncodedBodyParameter; + } + + /** + * Set the {@link AuthenticationDetailsSource} to use. Defaults to + * {@link WebAuthenticationDetailsSource}. + * @param authenticationDetailsSource the {@code AuthenticationDetailsSource} to use + */ + public void setAuthenticationDetailsSource( + AuthenticationDetailsSource authenticationDetailsSource) { + Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null"); + this.authenticationDetailsSource = authenticationDetailsSource; + } + +} diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java index 9cad61d0cb..5aa819f6be 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java @@ -44,6 +44,7 @@ import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthen import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; @@ -83,12 +84,12 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { private AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationEntryPointFailureHandler( (request, response, exception) -> this.authenticationEntryPoint.commence(request, response, exception)); - private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver(); - private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository(); + private AuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter(); + /** * Construct a {@code BearerTokenAuthenticationFilter} using the provided parameter(s) * @param authenticationManagerResolver @@ -121,24 +122,22 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String token; + Authentication authenticationRequest; try { - token = this.bearerTokenResolver.resolve(request); + authenticationRequest = this.authenticationConverter.convert(request); } catch (OAuth2AuthenticationException invalid) { this.logger.trace("Sending to authentication entry point since failed to resolve bearer token", invalid); this.authenticationEntryPoint.commence(request, response, invalid); return; } - if (token == null) { + + if (authenticationRequest == null) { this.logger.trace("Did not process request since did not find bearer token"); filterChain.doFilter(request, response); return; } - BearerTokenAuthenticationToken authenticationRequest = new BearerTokenAuthenticationToken(token); - authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); - try { AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request); Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest); @@ -194,7 +193,14 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { */ public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) { Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); - this.bearerTokenResolver = bearerTokenResolver; + this.authenticationConverter = (request) -> { + String token = bearerTokenResolver.resolve(request); + if (!StringUtils.hasText(token)) { + this.logger.trace("Did not process request since did not find bearer token"); + return null; + } + return new BearerTokenAuthenticationToken(token); + }; } /** @@ -243,4 +249,15 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { return StringUtils.hasText(jwkThumbprintClaim); } + /** + * Set the {@link AuthenticationConverter} to use. Defaults to + * {@link BearerTokenAuthenticationConverter}. + * @param authenticationConverter the {@code AuthenticationConverter} to use + * @since 6.5 + */ + public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { + Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); + this.authenticationConverter = authenticationConverter; + } + } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java new file mode 100644 index 0000000000..a5655a0c11 --- /dev/null +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.server.resource.web.authentication; + +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; + +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.Mockito.verify; + +/** + * Tests for {@link BearerTokenAuthenticationConverter} + * + * @author Max Batischev + */ +public class BearerTokenAuthenticationConverterTests { + + 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 BEARER_TOKEN = "test_bearer_token"; + + private final BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter(); + + @Test + public void convertWhenAuthorizationHeaderIsPresentThenTokenIsConverted() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + BEARER_TOKEN); + + Authentication authentication = this.converter.convert(request); + + assertThat(authentication).isNotNull(); + } + + @Test + public void convertWhenQueryParameterIsPresentThenTokenIsConverted() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod(HttpMethod.GET.name()); + request.addParameter("access_token", BEARER_TOKEN); + + this.converter.setAllowUriQueryParameter(true); + + Authentication authentication = this.converter.convert(request); + assertThat(authentication).isNotNull(); + } + + @Test + public void convertWhenAuthorizationHeaderNotIsPresentThenTokenIsNotConverted() { + MockHttpServletRequest request = new MockHttpServletRequest(); + + Authentication authentication = this.converter.convert(request); + + assertThat(authentication).isNull(); + } + + @Test + public void convertWhenAuthorizationHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("access_token", BEARER_TOKEN); + request.setMethod(HttpMethod.GET.name()); + request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + BEARER_TOKEN); + + assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request)) + .withMessageContaining("Found multiple bearer tokens in the request"); + } + + @Test + public void convertWhenXAuthTokenHeaderIsPresentAndBearerTokenHeaderNameSetThenTokenIsConverted() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(X_AUTH_TOKEN_HEADER, "Bearer " + TEST_X_AUTH_TOKEN); + + this.converter.setBearerTokenHeaderName(X_AUTH_TOKEN_HEADER); + + Authentication authentication = this.converter.convert(request); + assertThat(authentication).isNotNull(); + } + + @Test + public void convertWhenHeaderWithMissingTokenIsPresentThenAuthenticationExceptionIsThrown() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer "); + + assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request)) + .withMessageContaining(("Bearer token is malformed")); + } + + @Test + public void convertWhenHeaderWithInvalidCharactersIsPresentThenAuthenticationExceptionIsThrown() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer an\"invalid\"token"); + + assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request)) + .withMessageContaining(("Bearer token is malformed")); + } + + @Test + @SuppressWarnings("unchecked") + public void convertWhenCustomAuthenticationDetailsSourceSetThenTokenIsConverted() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + BEARER_TOKEN); + AuthenticationDetailsSource authenticationDetailsSource = Mockito + .mock(AuthenticationDetailsSource.class); + this.converter.setAuthenticationDetailsSource(authenticationDetailsSource); + + Authentication authentication = this.converter.convert(request); + + verify(authenticationDetailsSource).buildDetails(any()); + assertThat(authentication).isNotNull(); + } + + @Test + public void convertWhenFormParameterIsPresentAndAllowFormEncodedBodyParameterThenConverted() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod(HttpMethod.POST.name()); + request.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE); + request.addParameter("access_token", BEARER_TOKEN); + this.converter.setAllowFormEncodedBodyParameter(true); + + assertThat(this.converter.convert(request)).isNotNull(); + } + +} diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java index cc7477684f..465b5261c6 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java @@ -293,6 +293,16 @@ public class BearerTokenAuthenticationFilterTests { // @formatter:on } + @Test + public void setConverterWhenNullThenThrowsException() { + // @formatter:off + BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager); + assertThatIllegalArgumentException() + .isThrownBy(() -> filter.setAuthenticationConverter(null)) + .withMessageContaining("authenticationConverter cannot be null"); + // @formatter:on + } + @Test public void constructorWhenNullAuthenticationManagerThenThrowsException() { // @formatter:off From 30577bd291781d34b4550404872cfba657278edc Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Fri, 11 Apr 2025 15:36:23 +0300 Subject: [PATCH 087/149] Add Additional Tests To BearerTokenAuthenticationFilterTests Issue gh-14750 Signed-off-by: Max Batischev --- .../BearerTokenAuthenticationFilterTests.java | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java index 465b5261c6..b64a29f762 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java @@ -52,6 +52,7 @@ import org.springframework.security.oauth2.server.resource.authentication.Bearer import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; @@ -74,6 +75,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; @ExtendWith(MockitoExtension.class) public class BearerTokenAuthenticationFilterTests { + private static final String TEST_TOKEN = "token"; + @Mock AuthenticationEntryPoint authenticationEntryPoint; @@ -92,6 +95,9 @@ public class BearerTokenAuthenticationFilterTests { @Mock AuthenticationDetailsSource authenticationDetailsSource; + @Mock + AuthenticationConverter authenticationConverter; + MockHttpServletRequest request; MockHttpServletResponse response; @@ -321,6 +327,171 @@ public class BearerTokenAuthenticationFilterTests { // @formatter:on } + @Test + public void doFilterWhenBearerTokenPresentAndConverterSetThenAuthenticates() throws ServletException, IOException { + given(this.authenticationConverter.convert(this.request)) + .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); + BearerTokenAuthenticationFilter filter = addMocksWithConverter( + new BearerTokenAuthenticationFilter(this.authenticationManager)); + + filter.doFilter(this.request, this.response, this.filterChain); + + ArgumentCaptor captor = ArgumentCaptor + .forClass(BearerTokenAuthenticationToken.class); + verify(this.authenticationManager).authenticate(captor.capture()); + assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN); + assertThat(this.request.getAttribute(RequestAttributeSecurityContextRepository.DEFAULT_REQUEST_ATTR_NAME)) + .isNotNull(); + } + + @Test + public void doFilterWhenSecurityContextRepositoryAndConverterSetThenSaves() throws ServletException, IOException { + SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class); + given(this.authenticationConverter.convert(this.request)) + .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); + TestingAuthenticationToken expectedAuthentication = new TestingAuthenticationToken("test", "password"); + given(this.authenticationManager.authenticate(any())).willReturn(expectedAuthentication); + BearerTokenAuthenticationFilter filter = addMocksWithConverter( + new BearerTokenAuthenticationFilter(this.authenticationManager)); + filter.setSecurityContextRepository(securityContextRepository); + + filter.doFilter(this.request, this.response, this.filterChain); + + ArgumentCaptor captor = ArgumentCaptor + .forClass(BearerTokenAuthenticationToken.class); + verify(this.authenticationManager).authenticate(captor.capture()); + assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN); + ArgumentCaptor contextArg = ArgumentCaptor.forClass(SecurityContext.class); + verify(securityContextRepository).saveContext(contextArg.capture(), eq(this.request), eq(this.response)); + assertThat(contextArg.getValue().getAuthentication().getName()).isEqualTo(expectedAuthentication.getName()); + } + + @Test + public void doFilterWhenUsingAuthenticationManagerResolverAndConverterSetThenAuthenticates() throws Exception { + BearerTokenAuthenticationFilter filter = addMocksWithConverter( + new BearerTokenAuthenticationFilter(this.authenticationManagerResolver)); + given(this.authenticationConverter.convert(this.request)) + .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); + given(this.authenticationManagerResolver.resolve(any())).willReturn(this.authenticationManager); + + filter.doFilter(this.request, this.response, this.filterChain); + + ArgumentCaptor captor = ArgumentCaptor + .forClass(BearerTokenAuthenticationToken.class); + verify(this.authenticationManager).authenticate(captor.capture()); + assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN); + assertThat(this.request.getAttribute(RequestAttributeSecurityContextRepository.DEFAULT_REQUEST_ATTR_NAME)) + .isNotNull(); + } + + @Test + public void doFilterWhenNoBearerTokenPresentAndConverterSetThenDoesNotAuthenticate() + throws ServletException, IOException { + given(this.authenticationConverter.convert(this.request)).willReturn(null); + BearerTokenAuthenticationFilter filter = addMocksWithConverter( + new BearerTokenAuthenticationFilter(this.authenticationManager)); + + filter.doFilter(this.request, this.response, this.filterChain); + + verifyNoMoreInteractions(this.authenticationManager); + } + + @Test + public void doFilterWhenMalformedBearerTokenAndConverterSetThenPropagatesError() + throws ServletException, IOException { + BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_REQUEST, HttpStatus.BAD_REQUEST, + "description", "uri"); + OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error); + given(this.authenticationConverter.convert(this.request)).willThrow(exception); + BearerTokenAuthenticationFilter filter = addMocksWithConverter( + new BearerTokenAuthenticationFilter(this.authenticationManager)); + filter.doFilter(this.request, this.response, this.filterChain); + + verifyNoMoreInteractions(this.authenticationManager); + verify(this.authenticationEntryPoint).commence(this.request, this.response, exception); + } + + @Test + public void doFilterWhenAuthenticationFailsWithDefaultHandlerAndConverterSetThenPropagatesError() + throws ServletException, IOException { + BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED, + "description", "uri"); + OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error); + given(this.authenticationConverter.convert(this.request)) + .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); + given(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class))).willThrow(exception); + BearerTokenAuthenticationFilter filter = addMocksWithConverter( + new BearerTokenAuthenticationFilter(this.authenticationManager)); + + filter.doFilter(this.request, this.response, this.filterChain); + + verify(this.authenticationEntryPoint).commence(this.request, this.response, exception); + } + + @Test + public void doFilterWhenAuthenticationFailsWithCustomHandlerAndConverterSetThenPropagatesError() + throws ServletException, IOException { + BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED, + "description", "uri"); + OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error); + given(this.authenticationConverter.convert(this.request)) + .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); + given(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class))).willThrow(exception); + BearerTokenAuthenticationFilter filter = addMocksWithConverter( + new BearerTokenAuthenticationFilter(this.authenticationManager)); + filter.setAuthenticationFailureHandler(this.authenticationFailureHandler); + + filter.doFilter(this.request, this.response, this.filterChain); + + verify(this.authenticationFailureHandler).onAuthenticationFailure(this.request, this.response, exception); + } + + @Test + public void doFilterWhenConverterSetAndAuthenticationServiceExceptionThenRethrows() { + AuthenticationServiceException exception = new AuthenticationServiceException("message"); + given(this.authenticationConverter.convert(this.request)) + .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); + given(this.authenticationManager.authenticate(any())).willThrow(exception); + BearerTokenAuthenticationFilter filter = addMocksWithConverter( + new BearerTokenAuthenticationFilter(this.authenticationManager)); + + assertThatExceptionOfType(AuthenticationServiceException.class) + .isThrownBy(() -> filter.doFilter(this.request, this.response, this.filterChain)); + } + + @Test + public void doFilterWhenConverterSetAndCustomEntryPointAndAuthenticationErrorThenUses() + throws ServletException, IOException { + AuthenticationException exception = new InvalidBearerTokenException("message"); + given(this.authenticationConverter.convert(this.request)) + .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); + given(this.authenticationManager.authenticate(any())).willThrow(exception); + BearerTokenAuthenticationFilter filter = addMocksWithConverter( + new BearerTokenAuthenticationFilter(this.authenticationManager)); + AuthenticationEntryPoint entrypoint = mock(AuthenticationEntryPoint.class); + filter.setAuthenticationEntryPoint(entrypoint); + + filter.doFilter(this.request, this.response, this.filterChain); + + verify(entrypoint).commence(any(), any(), any(InvalidBearerTokenException.class)); + } + + @Test + public void doFilterWhenConverterSetCustomSecurityContextHolderStrategyThenUses() + throws ServletException, IOException { + given(this.authenticationConverter.convert(this.request)) + .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); + BearerTokenAuthenticationFilter filter = addMocksWithConverter( + new BearerTokenAuthenticationFilter(this.authenticationManager)); + SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); + given(strategy.createEmptyContext()).willReturn(new SecurityContextImpl()); + filter.setSecurityContextHolderStrategy(strategy); + + filter.doFilter(this.request, this.response, this.filterChain); + + verify(strategy).setContext(any()); + } + private BearerTokenAuthenticationFilter addMocks(BearerTokenAuthenticationFilter filter) { filter.setAuthenticationEntryPoint(this.authenticationEntryPoint); filter.setBearerTokenResolver(this.bearerTokenResolver); @@ -335,4 +506,10 @@ public class BearerTokenAuthenticationFilterTests { verifyNoMoreInteractions(this.authenticationManager); } + private BearerTokenAuthenticationFilter addMocksWithConverter(BearerTokenAuthenticationFilter filter) { + filter.setAuthenticationEntryPoint(this.authenticationEntryPoint); + filter.setAuthenticationConverter(this.authenticationConverter); + return filter; + } + } From eaab42a73c988523120441678d1f74d256465c9a Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 4 Jun 2025 13:35:31 -0600 Subject: [PATCH 088/149] Polish BearerTokenAuthenticationConverter Support - Moved to BearerTokenAuthenticationFilter constructor to align with AuthenticationFilter - Undeprecated BearerTokenResolver to reduce number of migration scenarios - Updated to 7.0 schema - Added migration docs Issue gh-14750 --- .../OAuth2ResourceServerConfigurer.java | 37 ++-- ...th2ResourceServerBeanDefinitionParser.java | 12 +- .../security/config/spring-security-6.5.rnc | 3 - .../security/config/spring-security-6.5.xsd | 6 - .../security/config/spring-security-7.0.rnc | 3 + .../security/config/spring-security-7.0.xsd | 6 + .../OAuth2ResourceServerConfigurerTests.java | 16 +- .../ROOT/pages/migration/servlet/oauth2.adoc | 55 +++++ .../servlet/appendix/namespace/http.adoc | 6 +- .../resource/web/BearerTokenResolver.java | 5 +- .../BearerTokenAuthenticationConverter.java | 127 +---------- .../BearerTokenAuthenticationFilter.java | 92 +++++--- ...arerTokenAuthenticationConverterTests.java | 14 +- .../BearerTokenAuthenticationFilterTests.java | 197 ++---------------- 14 files changed, 203 insertions(+), 376 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index 8d44dfdcb5..c4b976bc99 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java @@ -23,8 +23,6 @@ import java.util.Map; import java.util.function.Supplier; import jakarta.servlet.http.HttpServletRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; import org.springframework.core.convert.converter.Converter; @@ -44,7 +42,6 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; @@ -69,7 +66,6 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.accept.HeaderContentNegotiationStrategy; @@ -200,13 +196,9 @@ public final class OAuth2ResourceServerConfigurer bearerTokenResolver(BearerTokenResolver bearerTokenResolver) { Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); - this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver); + this.authenticationConverter = new BearerTokenResolverHoldingAuthenticationConverter(bearerTokenResolver); return this; } @@ -214,7 +206,7 @@ public final class OAuth2ResourceServerConfigurer authenticationConverter(AuthenticationConverter authenticationConverter) { Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); @@ -299,10 +291,9 @@ public final class OAuth2ResourceServerConfigurer authenticationManager; } - BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver); AuthenticationConverter converter = getAuthenticationConverter(); this.requestMatcher.setAuthenticationConverter(converter); - filter.setAuthenticationConverter(converter); + BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver, converter); filter.setAuthenticationEntryPoint(this.authenticationEntryPoint); filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy()); filter = postProcess(filter); @@ -394,7 +385,7 @@ public final class OAuth2ResourceServerConfigurer 0) { BearerTokenResolver bearerTokenResolver = this.context.getBean(BearerTokenResolver.class); - this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver); + this.authenticationConverter = new BearerTokenResolverHoldingAuthenticationConverter(bearerTokenResolver); } else { this.authenticationConverter = new BearerTokenAuthenticationConverter(); @@ -404,7 +395,7 @@ public final class OAuth2ResourceServerConfigurer - - - Reference to a AuthenticationConverter - - - diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc index 15d15b191b..bbf8622dfe 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc @@ -650,6 +650,9 @@ oauth2-resource-server.attlist &= oauth2-resource-server.attlist &= ## Reference to a AuthenticationEntryPoint attribute entry-point-ref {xsd:token}? +oauth2-resource-server.attlist &= + ## Reference to a AuthenticationConverter + attribute authentication-converter-ref {xsd:token}? jwt = ## Configures JWT authentication diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd index 34556b5549..2e3d6cf275 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd @@ -1999,6 +1999,12 @@ + + + Reference to a AuthenticationConverter + + + diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index 5c655f5afd..23dde67586 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -2571,16 +2571,20 @@ public class OAuth2ResourceServerConfigurerTests { @Bean AuthenticationConverter authenticationConverterOne() { - BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter(); - converter.setAllowUriQueryParameter(true); - return converter; + DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); + resolver.setAllowUriQueryParameter(true); + BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter(); + authenticationConverter.setBearerTokenResolver(resolver); + return authenticationConverter; } @Bean AuthenticationConverter authenticationConverterTwo() { - BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter(); - converter.setAllowUriQueryParameter(true); - return converter; + DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); + resolver.setAllowUriQueryParameter(true); + BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter(); + authenticationConverter.setBearerTokenResolver(resolver); + return authenticationConverter; } } diff --git a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc index 6cdb9043dd..293abadddb 100644 --- a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc @@ -115,3 +115,58 @@ fun authenticationConverter(val registrations: RelyingPartyRegistrationRepositor ====== If you must continue using `Saml2AuthenticationTokenConverter`, `OpenSaml4AuthenticationTokenConverter`, or `OpenSaml5AuthenticationTokenConverter` to process GET requests, you can call `setShouldConvertGetRequests` to `true.` + +== Provide an AuthenticationConverter to BearerTokenAuthenticationFilter + +In Spring Security 7, `BearerTokenAuthenticationFilter#setBearerTokenResolver` and `#setAuthenticaionDetailsSource` are deprecated in favor of configuring those on `BearerTokenAuthenticationConverter`. + +The `oauth2ResourceServer` DSL addresses most use cases and you need to nothing. + +If you are setting a `BearerTokenResolver` or `AuthenticationDetailsSource` directly on `BearerTokenAuthenticationFilter` similar to the following: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(authenticationManager); +filter.setBearerTokenResolver(myBearerTokenResolver); +filter.setAuthenticationDetailsSource(myAuthenticationDetailsSource); +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +val filter = BearerTokenAuthenticationFilter(authenticationManager) +filter.setBearerTokenResolver(myBearerTokenResolver) +filter.setAuthenticationDetailsSource(myAuthenticationDetailsSource) +---- +====== + +you are encouraged to use `BearerTokenAuthenticationConverter` to specify both: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +BearerTokenAuthenticationConverter authenticationConverter = + new BearerTokenAuthenticationConverter(); +authenticationConverter.setBearerTokenResolver(myBearerTokenResolver); +authenticationConverter.setAuthenticationDetailsSource(myAuthenticationDetailsSource); +BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(authenticationManager, authenicationConverter); +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +val authenticationConverter = BearerTokenAuthenticationConverter() +authenticationConverter.setBearerTokenResolver(myBearerTokenResolver) +authenticationConverter.setAuthenticationDetailsSource(myAuthenticationDetailsSource) +val filter = BearerTokenAuthenticationFilter(authenticationManager, authenticationConverter) +---- +====== diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index d5438e0f43..8979d5ad29 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -1266,7 +1266,8 @@ Reference to an `AuthenticationManagerResolver` which will resolve the `Authenti [[nsa-oauth2-resource-server-bearer-token-resolver-ref]] * **bearer-token-resolver-ref** -Reference to a `BearerTokenResolver` which will retrieve the bearer token from the request +Reference to a `BearerTokenResolver` which will retrieve the bearer token from the request. +This cannot be used in conjunction with `authentication-converter-ref` [[nsa-oauth2-resource-server-entry-point-ref]] * **entry-point-ref** @@ -1274,7 +1275,8 @@ Reference to a `AuthenticationEntryPoint` which will handle unauthorized request [[nsa-oauth2-resource-server-authentication-converter-ref]] * **authentication-converter-ref** -Reference to a `AuthenticationConverter` which convert request to authentication +Reference to a `AuthenticationConverter` which convert request to authentication. +This cannot be used in conjunction with `bearer-token-resolver-ref` [[nsa-jwt]] == diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java index 0fd023c5f6..7abd174630 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2025 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. @@ -29,10 +29,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; * @since 5.1 * @see RFC 6750 * Section 2: Authenticated Requests - * @deprecated Use - * {@link org.springframework.security.web.authentication.AuthenticationConverter} instead */ -@Deprecated @FunctionalInterface public interface BearerTokenResolver { diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java index 211a49bf21..9f7e91a40a 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java @@ -16,20 +16,13 @@ package org.springframework.security.oauth2.server.resource.web.authentication; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import jakarta.servlet.http.HttpServletRequest; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.server.resource.BearerTokenError; -import org.springframework.security.oauth2.server.resource.BearerTokenErrors; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; +import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; +import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.util.Assert; @@ -40,131 +33,29 @@ import org.springframework.util.StringUtils; * {@link BearerTokenAuthenticationToken} * * @author Max Batischev - * @since 6.5 + * @author Josh Cummings + * @since 7.0 */ public final class BearerTokenAuthenticationConverter implements AuthenticationConverter { private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?[a-zA-Z0-9-._~+/]+=*)$", - Pattern.CASE_INSENSITIVE); - - private static final String ACCESS_TOKEN_PARAMETER_NAME = "access_token"; - - private boolean allowFormEncodedBodyParameter = false; - - private boolean allowUriQueryParameter = false; - - private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION; + private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver(); @Override public Authentication convert(HttpServletRequest request) { - String token = resolveToken(request); + String token = this.bearerTokenResolver.resolve(request); if (StringUtils.hasText(token)) { BearerTokenAuthenticationToken authenticationToken = new BearerTokenAuthenticationToken(token); authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); - return authenticationToken; } return null; } - private String resolveToken(HttpServletRequest request) { - final String authorizationHeaderToken = resolveFromAuthorizationHeader(request); - final String parameterToken = isParameterTokenSupportedForRequest(request) - ? resolveFromRequestParameters(request) : null; - if (authorizationHeaderToken != null) { - if (parameterToken != null) { - final BearerTokenError error = BearerTokenErrors - .invalidRequest("Found multiple bearer tokens in the request"); - throw new OAuth2AuthenticationException(error); - } - return authorizationHeaderToken; - } - if (parameterToken != null && isParameterTokenEnabledForRequest(request)) { - return parameterToken; - } - return null; - } - - private String resolveFromAuthorizationHeader(HttpServletRequest request) { - String authorization = request.getHeader(this.bearerTokenHeaderName); - if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) { - return null; - } - Matcher matcher = authorizationPattern.matcher(authorization); - if (!matcher.matches()) { - BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed"); - throw new OAuth2AuthenticationException(error); - } - return matcher.group("token"); - } - - private boolean isParameterTokenEnabledForRequest(HttpServletRequest request) { - return ((this.allowFormEncodedBodyParameter && isFormEncodedRequest(request) && !isGetRequest(request) - && !hasAccessTokenInQueryString(request)) || (this.allowUriQueryParameter && isGetRequest(request))); - } - - private static String resolveFromRequestParameters(HttpServletRequest request) { - String[] values = request.getParameterValues(ACCESS_TOKEN_PARAMETER_NAME); - if (values == null || values.length == 0) { - return null; - } - if (values.length == 1) { - return values[0]; - } - BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request"); - throw new OAuth2AuthenticationException(error); - } - - private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) { - return isFormEncodedRequest(request) || isGetRequest(request); - } - - private boolean isGetRequest(HttpServletRequest request) { - return HttpMethod.GET.name().equals(request.getMethod()); - } - - private boolean isFormEncodedRequest(HttpServletRequest request) { - return MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()); - } - - private static boolean hasAccessTokenInQueryString(HttpServletRequest request) { - return (request.getQueryString() != null) && request.getQueryString().contains(ACCESS_TOKEN_PARAMETER_NAME); - } - - /** - * Set if transport of access token using URI query parameter is supported. Defaults - * to {@code false}. - * - * The spec recommends against using this mechanism for sending bearer tokens, and - * even goes as far as stating that it was only included for completeness. - * @param allowUriQueryParameter if the URI query parameter is supported - */ - public void setAllowUriQueryParameter(boolean allowUriQueryParameter) { - this.allowUriQueryParameter = allowUriQueryParameter; - } - - /** - * Set this value to configure what header is checked when resolving a Bearer Token. - * This value is defaulted to {@link HttpHeaders#AUTHORIZATION}. - * - * This allows other headers to be used as the Bearer Token source such as - * {@link HttpHeaders#PROXY_AUTHORIZATION} - * @param bearerTokenHeaderName the header to check when retrieving the Bearer Token. - */ - public void setBearerTokenHeaderName(String bearerTokenHeaderName) { - this.bearerTokenHeaderName = bearerTokenHeaderName; - } - - /** - * Set if transport of access token using form-encoded body parameter is supported. - * Defaults to {@code false}. - * @param allowFormEncodedBodyParameter if the form-encoded body parameter is - * supported - */ - public void setAllowFormEncodedBodyParameter(boolean allowFormEncodedBodyParameter) { - this.allowFormEncodedBodyParameter = allowFormEncodedBodyParameter; + public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) { + Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); + this.bearerTokenResolver = bearerTokenResolver; } /** diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java index 5aa819f6be..6a5f5c4869 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java @@ -40,6 +40,7 @@ import org.springframework.security.oauth2.server.resource.BearerTokenErrors; import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; +import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; @@ -76,6 +77,8 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { private final AuthenticationManagerResolver authenticationManagerResolver; + private final AuthenticationConverter authenticationConverter; + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); @@ -84,20 +87,15 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { private AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationEntryPointFailureHandler( (request, response, exception) -> this.authenticationEntryPoint.commence(request, response, exception)); - private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository(); - private AuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter(); - /** * Construct a {@code BearerTokenAuthenticationFilter} using the provided parameter(s) * @param authenticationManagerResolver */ public BearerTokenAuthenticationFilter( AuthenticationManagerResolver authenticationManagerResolver) { - Assert.notNull(authenticationManagerResolver, "authenticationManagerResolver cannot be null"); - this.authenticationManagerResolver = authenticationManagerResolver; + this(authenticationManagerResolver, new BearerTokenAuthenticationConverter()); } /** @@ -105,8 +103,43 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { * @param authenticationManager */ public BearerTokenAuthenticationFilter(AuthenticationManager authenticationManager) { + this(authenticationManager, new BearerTokenAuthenticationConverter()); + } + + /** + * Construct this filter using the provided parameters + * @param authenticationManager the {@link AuthenticationManager} to use + * @param authenticationConverter the {@link AuthenticationConverter} to use + * @since 7.0 + * @see JwtAuthenticationProvider + * @see OpaqueTokenAuthenticationProvider + * @see BearerTokenAuthenticationConverter + */ + public BearerTokenAuthenticationFilter(AuthenticationManager authenticationManager, + AuthenticationConverter authenticationConverter) { Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - this.authenticationManagerResolver = (request) -> authenticationManager; + Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); + this.authenticationManagerResolver = (authentication) -> authenticationManager; + this.authenticationConverter = authenticationConverter; + } + + /** + * Construct this filter using the provided parameters + * @param authenticationManagerResolver the {@link AuthenticationManagerResolver} to + * use + * @param authenticationConverter the {@link AuthenticationConverter} to use + * @since 7.0 + * @see JwtAuthenticationProvider + * @see OpaqueTokenAuthenticationProvider + * @see BearerTokenAuthenticationConverter + */ + public BearerTokenAuthenticationFilter( + AuthenticationManagerResolver authenticationManagerResolver, + AuthenticationConverter authenticationConverter) { + Assert.notNull(authenticationManagerResolver, "authenticationManagerResolver cannot be null"); + Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); + this.authenticationManagerResolver = authenticationManagerResolver; + this.authenticationConverter = authenticationConverter; } /** @@ -190,17 +223,20 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { * Set the {@link BearerTokenResolver} to use. Defaults to * {@link DefaultBearerTokenResolver}. * @param bearerTokenResolver the {@code BearerTokenResolver} to use + * @deprecated Please provide an {@link AuthenticationConverter} in the constructor + * instead + * @see BearerTokenAuthenticationConverter */ + @Deprecated public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) { Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); - this.authenticationConverter = (request) -> { - String token = bearerTokenResolver.resolve(request); - if (!StringUtils.hasText(token)) { - this.logger.trace("Did not process request since did not find bearer token"); - return null; - } - return new BearerTokenAuthenticationToken(token); - }; + if (this.authenticationConverter instanceof BearerTokenAuthenticationConverter converter) { + converter.setBearerTokenResolver(bearerTokenResolver); + } + else { + throw new IllegalArgumentException( + "You cannot both specify an AuthenticationConverter and a BearerTokenResolver."); + } } /** @@ -227,13 +263,24 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { /** * Set the {@link AuthenticationDetailsSource} to use. Defaults to * {@link WebAuthenticationDetailsSource}. - * @param authenticationDetailsSource the {@code AuthenticationConverter} to use + * @param authenticationDetailsSource the {@code AuthenticationDetailsSource} to use * @since 5.5 + * @deprecated Please provide an {@link AuthenticationConverter} in the constructor + * and set the {@link AuthenticationDetailsSource} there instead. For example, you can + * use {@link BearerTokenAuthenticationConverter#setAuthenticationDetailsSource} + * @see BearerTokenAuthenticationConverter */ + @Deprecated public void setAuthenticationDetailsSource( AuthenticationDetailsSource authenticationDetailsSource) { Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null"); - this.authenticationDetailsSource = authenticationDetailsSource; + if (this.authenticationConverter instanceof BearerTokenAuthenticationConverter converter) { + converter.setAuthenticationDetailsSource(authenticationDetailsSource); + } + else { + throw new IllegalArgumentException( + "You cannot specify both an AuthenticationConverter and an AuthenticationDetailsSource"); + } } private static boolean isDPoPBoundAccessToken(Authentication authentication) { @@ -249,15 +296,4 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { return StringUtils.hasText(jwkThumbprintClaim); } - /** - * Set the {@link AuthenticationConverter} to use. Defaults to - * {@link BearerTokenAuthenticationConverter}. - * @param authenticationConverter the {@code AuthenticationConverter} to use - * @since 6.5 - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java index a5655a0c11..061f3b2232 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java @@ -27,6 +27,7 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -46,8 +47,14 @@ public class BearerTokenAuthenticationConverterTests { private static final String BEARER_TOKEN = "test_bearer_token"; + private final DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); + private final BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter(); + { + this.converter.setBearerTokenResolver(this.resolver); + } + @Test public void convertWhenAuthorizationHeaderIsPresentThenTokenIsConverted() { MockHttpServletRequest request = new MockHttpServletRequest(); @@ -64,7 +71,7 @@ public class BearerTokenAuthenticationConverterTests { request.setMethod(HttpMethod.GET.name()); request.addParameter("access_token", BEARER_TOKEN); - this.converter.setAllowUriQueryParameter(true); + this.resolver.setAllowUriQueryParameter(true); Authentication authentication = this.converter.convert(request); assertThat(authentication).isNotNull(); @@ -86,6 +93,7 @@ public class BearerTokenAuthenticationConverterTests { request.setMethod(HttpMethod.GET.name()); request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + BEARER_TOKEN); + this.resolver.setAllowUriQueryParameter(true); assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request)) .withMessageContaining("Found multiple bearer tokens in the request"); } @@ -95,7 +103,7 @@ public class BearerTokenAuthenticationConverterTests { MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader(X_AUTH_TOKEN_HEADER, "Bearer " + TEST_X_AUTH_TOKEN); - this.converter.setBearerTokenHeaderName(X_AUTH_TOKEN_HEADER); + this.resolver.setBearerTokenHeaderName(X_AUTH_TOKEN_HEADER); Authentication authentication = this.converter.convert(request); assertThat(authentication).isNotNull(); @@ -140,7 +148,7 @@ public class BearerTokenAuthenticationConverterTests { request.setMethod(HttpMethod.POST.name()); request.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE); request.addParameter("access_token", BEARER_TOKEN); - this.converter.setAllowFormEncodedBodyParameter(true); + this.resolver.setAllowFormEncodedBodyParameter(true); assertThat(this.converter.convert(request)).isNotNull(); } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java index b64a29f762..67131e21b6 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java @@ -75,8 +75,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; @ExtendWith(MockitoExtension.class) public class BearerTokenAuthenticationFilterTests { - private static final String TEST_TOKEN = "token"; - @Mock AuthenticationEntryPoint authenticationEntryPoint; @@ -95,9 +93,6 @@ public class BearerTokenAuthenticationFilterTests { @Mock AuthenticationDetailsSource authenticationDetailsSource; - @Mock - AuthenticationConverter authenticationConverter; - MockHttpServletRequest request; MockHttpServletResponse response; @@ -269,6 +264,24 @@ public class BearerTokenAuthenticationFilterTests { assertThat(error.getDescription()).isEqualTo("Invalid bearer token"); } + @Test + public void doFilterWhenSetAuthenticationConverterAndAuthenticationDetailsSourceThenIllegalArgument( + @Mock AuthenticationConverter authenticationConverter) { + BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager, + authenticationConverter); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> filter.setAuthenticationDetailsSource(this.authenticationDetailsSource)); + } + + @Test + public void doFilterWhenSetBearerTokenResolverAndAuthenticationConverterThenIllegalArgument( + @Mock AuthenticationConverter authenticationConverter) { + BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager, + authenticationConverter); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> filter.setBearerTokenResolver(this.bearerTokenResolver)); + } + @Test public void setAuthenticationEntryPointWhenNullThenThrowsException() { BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager); @@ -302,9 +315,8 @@ public class BearerTokenAuthenticationFilterTests { @Test public void setConverterWhenNullThenThrowsException() { // @formatter:off - BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager); assertThatIllegalArgumentException() - .isThrownBy(() -> filter.setAuthenticationConverter(null)) + .isThrownBy(() -> new BearerTokenAuthenticationFilter(this.authenticationManager, null)) .withMessageContaining("authenticationConverter cannot be null"); // @formatter:on } @@ -327,171 +339,6 @@ public class BearerTokenAuthenticationFilterTests { // @formatter:on } - @Test - public void doFilterWhenBearerTokenPresentAndConverterSetThenAuthenticates() throws ServletException, IOException { - given(this.authenticationConverter.convert(this.request)) - .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); - BearerTokenAuthenticationFilter filter = addMocksWithConverter( - new BearerTokenAuthenticationFilter(this.authenticationManager)); - - filter.doFilter(this.request, this.response, this.filterChain); - - ArgumentCaptor captor = ArgumentCaptor - .forClass(BearerTokenAuthenticationToken.class); - verify(this.authenticationManager).authenticate(captor.capture()); - assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN); - assertThat(this.request.getAttribute(RequestAttributeSecurityContextRepository.DEFAULT_REQUEST_ATTR_NAME)) - .isNotNull(); - } - - @Test - public void doFilterWhenSecurityContextRepositoryAndConverterSetThenSaves() throws ServletException, IOException { - SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class); - given(this.authenticationConverter.convert(this.request)) - .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); - TestingAuthenticationToken expectedAuthentication = new TestingAuthenticationToken("test", "password"); - given(this.authenticationManager.authenticate(any())).willReturn(expectedAuthentication); - BearerTokenAuthenticationFilter filter = addMocksWithConverter( - new BearerTokenAuthenticationFilter(this.authenticationManager)); - filter.setSecurityContextRepository(securityContextRepository); - - filter.doFilter(this.request, this.response, this.filterChain); - - ArgumentCaptor captor = ArgumentCaptor - .forClass(BearerTokenAuthenticationToken.class); - verify(this.authenticationManager).authenticate(captor.capture()); - assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN); - ArgumentCaptor contextArg = ArgumentCaptor.forClass(SecurityContext.class); - verify(securityContextRepository).saveContext(contextArg.capture(), eq(this.request), eq(this.response)); - assertThat(contextArg.getValue().getAuthentication().getName()).isEqualTo(expectedAuthentication.getName()); - } - - @Test - public void doFilterWhenUsingAuthenticationManagerResolverAndConverterSetThenAuthenticates() throws Exception { - BearerTokenAuthenticationFilter filter = addMocksWithConverter( - new BearerTokenAuthenticationFilter(this.authenticationManagerResolver)); - given(this.authenticationConverter.convert(this.request)) - .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); - given(this.authenticationManagerResolver.resolve(any())).willReturn(this.authenticationManager); - - filter.doFilter(this.request, this.response, this.filterChain); - - ArgumentCaptor captor = ArgumentCaptor - .forClass(BearerTokenAuthenticationToken.class); - verify(this.authenticationManager).authenticate(captor.capture()); - assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN); - assertThat(this.request.getAttribute(RequestAttributeSecurityContextRepository.DEFAULT_REQUEST_ATTR_NAME)) - .isNotNull(); - } - - @Test - public void doFilterWhenNoBearerTokenPresentAndConverterSetThenDoesNotAuthenticate() - throws ServletException, IOException { - given(this.authenticationConverter.convert(this.request)).willReturn(null); - BearerTokenAuthenticationFilter filter = addMocksWithConverter( - new BearerTokenAuthenticationFilter(this.authenticationManager)); - - filter.doFilter(this.request, this.response, this.filterChain); - - verifyNoMoreInteractions(this.authenticationManager); - } - - @Test - public void doFilterWhenMalformedBearerTokenAndConverterSetThenPropagatesError() - throws ServletException, IOException { - BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_REQUEST, HttpStatus.BAD_REQUEST, - "description", "uri"); - OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error); - given(this.authenticationConverter.convert(this.request)).willThrow(exception); - BearerTokenAuthenticationFilter filter = addMocksWithConverter( - new BearerTokenAuthenticationFilter(this.authenticationManager)); - filter.doFilter(this.request, this.response, this.filterChain); - - verifyNoMoreInteractions(this.authenticationManager); - verify(this.authenticationEntryPoint).commence(this.request, this.response, exception); - } - - @Test - public void doFilterWhenAuthenticationFailsWithDefaultHandlerAndConverterSetThenPropagatesError() - throws ServletException, IOException { - BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED, - "description", "uri"); - OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error); - given(this.authenticationConverter.convert(this.request)) - .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); - given(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class))).willThrow(exception); - BearerTokenAuthenticationFilter filter = addMocksWithConverter( - new BearerTokenAuthenticationFilter(this.authenticationManager)); - - filter.doFilter(this.request, this.response, this.filterChain); - - verify(this.authenticationEntryPoint).commence(this.request, this.response, exception); - } - - @Test - public void doFilterWhenAuthenticationFailsWithCustomHandlerAndConverterSetThenPropagatesError() - throws ServletException, IOException { - BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED, - "description", "uri"); - OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error); - given(this.authenticationConverter.convert(this.request)) - .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); - given(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class))).willThrow(exception); - BearerTokenAuthenticationFilter filter = addMocksWithConverter( - new BearerTokenAuthenticationFilter(this.authenticationManager)); - filter.setAuthenticationFailureHandler(this.authenticationFailureHandler); - - filter.doFilter(this.request, this.response, this.filterChain); - - verify(this.authenticationFailureHandler).onAuthenticationFailure(this.request, this.response, exception); - } - - @Test - public void doFilterWhenConverterSetAndAuthenticationServiceExceptionThenRethrows() { - AuthenticationServiceException exception = new AuthenticationServiceException("message"); - given(this.authenticationConverter.convert(this.request)) - .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); - given(this.authenticationManager.authenticate(any())).willThrow(exception); - BearerTokenAuthenticationFilter filter = addMocksWithConverter( - new BearerTokenAuthenticationFilter(this.authenticationManager)); - - assertThatExceptionOfType(AuthenticationServiceException.class) - .isThrownBy(() -> filter.doFilter(this.request, this.response, this.filterChain)); - } - - @Test - public void doFilterWhenConverterSetAndCustomEntryPointAndAuthenticationErrorThenUses() - throws ServletException, IOException { - AuthenticationException exception = new InvalidBearerTokenException("message"); - given(this.authenticationConverter.convert(this.request)) - .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); - given(this.authenticationManager.authenticate(any())).willThrow(exception); - BearerTokenAuthenticationFilter filter = addMocksWithConverter( - new BearerTokenAuthenticationFilter(this.authenticationManager)); - AuthenticationEntryPoint entrypoint = mock(AuthenticationEntryPoint.class); - filter.setAuthenticationEntryPoint(entrypoint); - - filter.doFilter(this.request, this.response, this.filterChain); - - verify(entrypoint).commence(any(), any(), any(InvalidBearerTokenException.class)); - } - - @Test - public void doFilterWhenConverterSetCustomSecurityContextHolderStrategyThenUses() - throws ServletException, IOException { - given(this.authenticationConverter.convert(this.request)) - .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN)); - BearerTokenAuthenticationFilter filter = addMocksWithConverter( - new BearerTokenAuthenticationFilter(this.authenticationManager)); - SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); - given(strategy.createEmptyContext()).willReturn(new SecurityContextImpl()); - filter.setSecurityContextHolderStrategy(strategy); - - filter.doFilter(this.request, this.response, this.filterChain); - - verify(strategy).setContext(any()); - } - private BearerTokenAuthenticationFilter addMocks(BearerTokenAuthenticationFilter filter) { filter.setAuthenticationEntryPoint(this.authenticationEntryPoint); filter.setBearerTokenResolver(this.bearerTokenResolver); @@ -506,10 +353,4 @@ public class BearerTokenAuthenticationFilterTests { verifyNoMoreInteractions(this.authenticationManager); } - private BearerTokenAuthenticationFilter addMocksWithConverter(BearerTokenAuthenticationFilter filter) { - filter.setAuthenticationEntryPoint(this.authenticationEntryPoint); - filter.setAuthenticationConverter(this.authenticationConverter); - return filter; - } - } From adca1b31be5569be03b1af3d79b8a82e99a7cb40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 03:52:18 +0000 Subject: [PATCH 089/149] Bump org.apache.maven:maven-resolver-provider from 3.9.9 to 3.9.10 Bumps org.apache.maven:maven-resolver-provider from 3.9.9 to 3.9.10. --- updated-dependencies: - dependency-name: org.apache.maven:maven-resolver-provider dependency-version: 3.9.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6a16532177..0109327663 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,7 +57,7 @@ org-apache-directory-server-apacheds-protocol-shared = { module = "org.apache.di org-apache-directory-server-apacheds-server-jndi = { module = "org.apache.directory.server:apacheds-server-jndi", version.ref = "org-apache-directory-server" } org-apache-directory-shared-shared-ldap = "org.apache.directory.shared:shared-ldap:0.9.15" org-apache-httpcomponents-httpclient = "org.apache.httpcomponents:httpclient:4.5.14" -org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.9" +org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.10" org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" } 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" } From eaf818414295f9bfc48232069c3b65794cded99a Mon Sep 17 00:00:00 2001 From: Liviu Gheorghe Date: Sun, 3 Mar 2024 22:21:07 +0200 Subject: [PATCH 090/149] Send saml logout response even when validation errors happen Signed-off-by: Liviu Gheorghe --- .../security/saml2/core/Saml2ErrorCodes.java | 26 +++- .../BaseOpenSamlLogoutResponseResolver.java | 25 +++- .../logout/Saml2LogoutRequestFilter.java | 103 ++++++++------- .../logout/Saml2LogoutResponseResolver.java | 14 ++- .../OpenSaml4LogoutResponseResolver.java | 10 ++ .../OpenSaml4LogoutResponseResolverTests.java | 51 +++++++- .../OpenSaml5LogoutResponseResolver.java | 10 ++ .../logout/Saml2LogoutRequestFilterTests.java | 119 ++++++++++++++++-- 8 files changed, 300 insertions(+), 58 deletions(-) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java index c45824442f..0777488d22 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -130,6 +130,30 @@ public final class Saml2ErrorCodes { */ public static final String INVALID_IN_RESPONSE_TO = "invalid_in_response_to"; + /** + * The RP registration does not have configured a logout request endpoint + * @since 6.3 + */ + public static final String MISSING_LOGOUT_REQUEST_ENDPOINT = "missing_logout_request_endpoint"; + + /** + * The saml response or logout request was delivered via an invalid binding + * @since 6.3 + */ + public static final String INVALID_BINDING = "invalid_binding"; + + /** + * The saml logout request failed validation + * @since 6.3 + */ + public static final String INVALID_LOGOUT_REQUEST = "invalid_logout_request"; + + /** + * The saml logout response could not be generated + * @since 6.3 + */ + public static final String FAILED_TO_GENERATE_LOGOUT_RESPONSE = "failed_to_generate_logout_response"; + private Saml2ErrorCodes() { } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java index 41a44a1856..71f974b816 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java @@ -43,8 +43,11 @@ import org.opensaml.saml.saml2.core.impl.StatusCodeBuilder; import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; @@ -130,6 +133,16 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes */ @Override public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication) { + return resolve(request, authentication, StatusCode.SUCCESS); + } + + @Override + public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication, + Saml2AuthenticationException authenticationException) { + return resolve(request, authentication, getSamlStatus(authenticationException)); + } + + private Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication, String statusCode) { LogoutRequest logoutRequest = this.saml.deserialize(extractSamlRequest(request)); String registrationId = getRegistrationId(authentication); RelyingPartyRegistration registration = this.relyingPartyRegistrationResolver.resolve(request, registrationId); @@ -152,7 +165,7 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes issuer.setValue(entityId); logoutResponse.setIssuer(issuer); StatusCode code = this.statusCodeBuilder.buildObject(); - code.setValue(StatusCode.SUCCESS); + code.setValue(statusCode); Status status = this.statusBuilder.buildObject(); status.setStatusCode(code); logoutResponse.setStatus(status); @@ -224,6 +237,16 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes return this.saml.serialize(logoutResponse).serialize(); } + private String getSamlStatus(Saml2AuthenticationException exception) { + Saml2Error saml2Error = exception.getSaml2Error(); + return switch (saml2Error.getErrorCode()) { + case Saml2ErrorCodes.MISSING_LOGOUT_REQUEST_ENDPOINT, Saml2ErrorCodes.INVALID_BINDING -> + StatusCode.REQUEST_DENIED; + case Saml2ErrorCodes.INVALID_LOGOUT_REQUEST -> StatusCode.REQUESTER; + default -> StatusCode.RESPONDER; + }; + } + static final class LogoutResponseParameters { private final HttpServletRequest request; diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java index 1476f37624..354fb375fc 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java @@ -31,6 +31,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; @@ -112,52 +113,26 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - Saml2LogoutRequestValidatorParameters parameters; try { - parameters = this.logoutRequestResolver.resolve(request, authentication); + Saml2LogoutRequestValidatorParameters parameters = this.logoutRequestResolver.resolve(request, + authentication); + if (parameters == null) { + chain.doFilter(request, response); + return; + } + + Saml2LogoutResponse logoutResponse = processLogoutRequest(request, response, authentication, parameters); + sendLogoutResponse(request, response, logoutResponse); } catch (Saml2AuthenticationException ex) { - this.logger.trace("Did not process logout request since failed to find requested RelyingPartyRegistration"); - response.sendError(HttpServletResponse.SC_BAD_REQUEST); - return; - } - if (parameters == null) { - chain.doFilter(request, response); - return; - } - RelyingPartyRegistration registration = parameters.getRelyingPartyRegistration(); - if (registration.getSingleLogoutServiceLocation() == null) { - this.logger.trace( - "Did not process logout request since RelyingPartyRegistration has not been configured with a logout request endpoint"); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED); - return; - } + Saml2LogoutResponse errorLogoutResponse = this.logoutResponseResolver.resolve(request, authentication, ex); + if (errorLogoutResponse == null) { + this.logger.trace("Returning error since no error logout response could be generated", ex); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } - Saml2MessageBinding saml2MessageBinding = Saml2MessageBindingUtils.resolveBinding(request); - if (!registration.getSingleLogoutServiceBindings().contains(saml2MessageBinding)) { - this.logger.trace("Did not process logout request since used incorrect binding"); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED); - return; - } - - Saml2LogoutValidatorResult result = this.logoutRequestValidator.validate(parameters); - if (result.hasErrors()) { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, result.getErrors().iterator().next().toString()); - this.logger.debug(LogMessage.format("Failed to validate LogoutRequest: %s", result.getErrors())); - return; - } - this.handler.logout(request, response, authentication); - Saml2LogoutResponse logoutResponse = this.logoutResponseResolver.resolve(request, authentication); - if (logoutResponse == null) { - this.logger.trace("Returning 401 since no logout response generated"); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - return; - } - if (logoutResponse.getBinding() == Saml2MessageBinding.REDIRECT) { - doRedirect(request, response, logoutResponse); - } - else { - doPost(response, logoutResponse); + sendLogoutResponse(request, response, errorLogoutResponse); } } @@ -180,6 +155,50 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter { this.securityContextHolderStrategy = securityContextHolderStrategy; } + private Saml2LogoutResponse processLogoutRequest(HttpServletRequest request, HttpServletResponse response, + Authentication authentication, Saml2LogoutRequestValidatorParameters parameters) { + RelyingPartyRegistration registration = parameters.getRelyingPartyRegistration(); + if (registration.getSingleLogoutServiceLocation() == null) { + this.logger.trace( + "Did not process logout request since RelyingPartyRegistration has not been configured with a logout request endpoint"); + throw new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.MISSING_LOGOUT_REQUEST_ENDPOINT, + "RelyingPartyRegistration has not been configured with a logout request endpoint")); + } + + Saml2MessageBinding saml2MessageBinding = Saml2MessageBindingUtils.resolveBinding(request); + if (!registration.getSingleLogoutServiceBindings().contains(saml2MessageBinding)) { + this.logger.trace("Did not process logout request since used incorrect binding"); + throw new Saml2AuthenticationException( + new Saml2Error(Saml2ErrorCodes.INVALID_BINDING, "Logout request used invalid binding")); + } + + Saml2LogoutValidatorResult result = this.logoutRequestValidator.validate(parameters); + if (result.hasErrors()) { + this.logger.debug(LogMessage.format("Failed to validate LogoutRequest: %s", result.getErrors())); + throw new Saml2AuthenticationException( + new Saml2Error(Saml2ErrorCodes.INVALID_LOGOUT_REQUEST, "Failed to validate the logout request")); + } + + this.handler.logout(request, response, authentication); + Saml2LogoutResponse logoutResponse = this.logoutResponseResolver.resolve(request, authentication); + if (logoutResponse == null) { + this.logger.trace("Returning error since no logout response generated"); + throw new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.FAILED_TO_GENERATE_LOGOUT_RESPONSE, + "Could not generated logout response")); + } + return logoutResponse; + } + + private void sendLogoutResponse(HttpServletRequest request, HttpServletResponse response, + Saml2LogoutResponse logoutResponse) throws IOException { + if (logoutResponse.getBinding() == Saml2MessageBinding.REDIRECT) { + doRedirect(request, response, logoutResponse); + } + else { + doPost(response, logoutResponse); + } + } + private void doRedirect(HttpServletRequest request, HttpServletResponse response, Saml2LogoutResponse logoutResponse) throws IOException { String location = logoutResponse.getResponseLocation(); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.java index f722fd55b7..c156ef928c 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -19,6 +19,7 @@ package org.springframework.security.saml2.provider.service.web.authentication.l import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -44,4 +45,15 @@ public interface Saml2LogoutResponseResolver { */ Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication); + /** + * Prepare to create, sign, and serialize a SAML 2.0 Error Logout Response. + * @param request the HTTP request + * @param authentication the current user + * @param authenticationException the thrown exception when the logout request was + * processed + * @return a signed and serialized SAML 2.0 Logout Response + */ + Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication, + Saml2AuthenticationException authenticationException); + } diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java index 4d70062b3f..c88f945dd8 100644 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java +++ b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java @@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.opensaml.saml.saml2.core.LogoutRequest; import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; @@ -66,6 +67,15 @@ public final class OpenSaml4LogoutResponseResolver implements Saml2LogoutRespons return this.delegate.resolve(request, authentication); } + /** + * {@inheritDoc} + */ + @Override + public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication, + Saml2AuthenticationException exception) { + return this.delegate.resolve(request, authentication, exception); + } + /** * Set a {@link Consumer} for modifying the OpenSAML {@link LogoutRequest} * @param parametersConsumer a consumer that accepts an diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java index eba07f55c5..b8907c5cd7 100644 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java +++ b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -17,17 +17,26 @@ package org.springframework.security.saml2.provider.service.web.authentication.logout; import java.util.function.Consumer; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opensaml.saml.saml2.core.LogoutRequest; +import org.opensaml.saml.saml2.core.StatusCode; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver.LogoutResponseParameters; @@ -69,6 +78,27 @@ public class OpenSaml4LogoutResponseResolverTests { verify(parametersConsumer).accept(any()); } + @ParameterizedTest + @MethodSource("provideAuthExceptionAndExpectedSamlStatusCode") + public void resolveWithAuthException(Saml2AuthenticationException exception, String expectedStatusCode) { + OpenSaml4LogoutResponseResolver logoutResponseResolver = new OpenSaml4LogoutResponseResolver( + this.relyingPartyRegistrationResolver); + MockHttpServletRequest request = new MockHttpServletRequest(); + RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration() + .assertingPartyMetadata( + (party) -> party.singleLogoutServiceResponseLocation("https://ap.example.com/logout") + .singleLogoutServiceBinding(Saml2MessageBinding.POST)) + .build(); + Authentication authentication = new TestingAuthenticationToken("user", "password"); + LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); + request.setParameter(Saml2ParameterNames.SAML_REQUEST, + Saml2Utils.samlEncode(this.saml.serialize(logoutRequest).serialize().getBytes())); + given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration); + Saml2LogoutResponse logoutResponse = logoutResponseResolver.resolve(request, authentication, exception); + assertThat(logoutResponse).isNotNull(); + assertThat(new String(Saml2Utils.samlDecode(logoutResponse.getSamlResponse()))).contains(expectedStatusCode); + } + @Test public void setParametersConsumerWhenNullThenIllegalArgument() { OpenSaml4LogoutRequestResolver logoutRequestResolver = new OpenSaml4LogoutRequestResolver( @@ -77,4 +107,23 @@ public class OpenSaml4LogoutResponseResolverTests { .isThrownBy(() -> logoutRequestResolver.setParametersConsumer(null)); } + private static Stream provideAuthExceptionAndExpectedSamlStatusCode() { + return Stream.of( + Arguments.of( + new Saml2AuthenticationException( + new Saml2Error(Saml2ErrorCodes.MISSING_LOGOUT_REQUEST_ENDPOINT, "")), + StatusCode.REQUEST_DENIED), + Arguments.of(new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.INVALID_BINDING, "")), + StatusCode.REQUEST_DENIED), + Arguments.of( + new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.INVALID_LOGOUT_REQUEST, "")), + StatusCode.REQUESTER), + Arguments.of( + new Saml2AuthenticationException( + new Saml2Error(Saml2ErrorCodes.FAILED_TO_GENERATE_LOGOUT_RESPONSE, "")), + StatusCode.RESPONDER) + + ); + } + } diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutResponseResolver.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutResponseResolver.java index 4d0b917597..36b3fc021f 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutResponseResolver.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutResponseResolver.java @@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.opensaml.saml.saml2.core.LogoutRequest; import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; @@ -66,6 +67,15 @@ public final class OpenSaml5LogoutResponseResolver implements Saml2LogoutRespons return this.delegate.resolve(request, authentication); } + /** + * {@inheritDoc} + */ + @Override + public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication, + Saml2AuthenticationException exception) { + return this.delegate.resolve(request, authentication, exception); + } + /** * Set a {@link Consumer} for modifying the OpenSAML {@link LogoutRequest} * @param parametersConsumer a consumer that accepts an diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilterTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilterTests.java index 6b6fcea452..1d6e151eac 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilterTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * 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. @@ -28,6 +28,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; @@ -40,6 +41,7 @@ import org.springframework.security.web.authentication.logout.LogoutHandler; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.mock; import static org.mockito.Mockito.verify; @@ -117,12 +119,7 @@ public class Saml2LogoutRequestFilterTests { verify(this.logoutRequestValidator).validate(any()); verify(this.logoutHandler).logout(any(), any(), any()); verify(this.logoutResponseResolver).resolve(any(), any()); - String content = response.getContentAsString(); - assertThat(content).contains(Saml2ParameterNames.SAML_RESPONSE); - assertThat(content).contains(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation()); - assertThat(content).contains( - " Date: Mon, 6 Jun 2022 19:09:42 +0200 Subject: [PATCH 098/149] SAML 2.0 Single Logout Uses Saml2AuthenticationInfo This allows SLO to be triggered without the authentication principal needing to implement a given interface. Issue gh-10820 --- .../saml2/Saml2LogoutConfigurer.java | 7 +- .../http/Saml2LogoutBeanDefinitionParser.java | 7 +- .../Saml2AuthenticatedPrincipal.java | 4 +- .../Saml2AuthenticationInfo.java | 78 +++++++++++++++++++ .../BaseOpenSamlLogoutRequestResolver.java | 17 ++-- ...outRequestValidatorParametersResolver.java | 10 +-- .../BaseOpenSamlLogoutResponseResolver.java | 11 +-- .../logout/Saml2LogoutRequestFilter.java | 10 +-- ...outRequestValidatorParametersResolver.java | 10 +-- 9 files changed, 108 insertions(+), 46 deletions(-) create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java index e69f8c825b..f8303a81b9 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java @@ -33,7 +33,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator; @@ -531,10 +531,7 @@ public final class Saml2LogoutConfigurer> @Override public boolean matches(HttpServletRequest request) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - if (authentication == null) { - return false; - } - return authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal; + return Saml2AuthenticationInfo.fromAuthentication(authentication) != null; } } diff --git a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java index bef7a69d19..dfeed0d981 100644 --- a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java @@ -31,7 +31,7 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter; @@ -236,10 +236,7 @@ final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser { @Override public boolean matches(HttpServletRequest request) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - if (authentication == null) { - return false; - } - return authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal; + return Saml2AuthenticationInfo.fromAuthentication(authentication) != null; } public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java index 205c5f8941..69eb9d7628 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java @@ -31,7 +31,7 @@ import org.springframework.util.CollectionUtils; * @author Clement Stoquart * @since 5.2.2 */ -public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal { +public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal, Saml2AuthenticationInfo { /** * Get the first value of Saml2 token attribute by name @@ -72,10 +72,12 @@ public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal { * @return the {@link RelyingPartyRegistration} identifier * @since 5.6 */ + @Override default String getRelyingPartyRegistrationId() { return null; } + @Override default List getSessionIndexes() { return Collections.emptyList(); } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java new file mode 100644 index 0000000000..309e2a909b --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java @@ -0,0 +1,78 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.authentication; + +import java.util.List; + +import org.opensaml.saml.saml2.core.SessionIndex; + +import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; + +/** + * Additional SAML 2.0 authentication information + * + *

+ * SAML 2.0 Single Logout requires that the {@link Authentication#getPrincipal() + * authenticated principal} or the {@link Authentication} itself implements this + * interface. + * + * @author Christian Schuster + */ +public interface Saml2AuthenticationInfo { + + /** + * Get the {@link RelyingPartyRegistration} identifier + * @return the {@link RelyingPartyRegistration} identifier + */ + String getRelyingPartyRegistrationId(); + + /** + * Get the {@link SessionIndex} values of the authenticated principal + * @return the {@link SessionIndex} values of the authenticated principal + */ + List getSessionIndexes(); + + /** + * Try to obtain a {@link Saml2AuthenticationInfo} instance from an + * {@link Authentication} + * + *

+ * The result is either the {@link Authentication#getPrincipal() authenticated + * principal}, the {@link Authentication} itself, or {@code null}. + * + *

+ * Returning {@code null} indicates that the given {@link Authentication} does not + * represent a SAML 2.0 authentication. + * @param authentication the {@link Authentication} + * @return the {@link Saml2AuthenticationInfo} or {@code null} if unavailable + */ + static Saml2AuthenticationInfo fromAuthentication(Authentication authentication) { + if (authentication == null) { + return null; + } + Object principal = authentication.getPrincipal(); + if (principal instanceof Saml2AuthenticationInfo) { + return (Saml2AuthenticationInfo) principal; + } + if (authentication instanceof Saml2AuthenticationInfo) { + return (Saml2AuthenticationInfo) authentication; + } + return null; + } + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java index 980cfa4ac6..c4550b632f 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java @@ -42,7 +42,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; @@ -149,9 +149,9 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol NameID nameId = this.nameIdBuilder.buildObject(); nameId.setValue(authentication.getName()); logoutRequest.setNameID(nameId); - if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal) { - Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); - for (String index : principal.getSessionIndexes()) { + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + for (String index : info.getSessionIndexes()) { SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject(); sessionIndex.setValue(index); logoutRequest.getSessionIndexes().add(sessionIndex); @@ -191,12 +191,9 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol if (this.logger.isTraceEnabled()) { this.logger.trace("Attempting to resolve registrationId from " + authentication); } - if (authentication == null) { - return null; - } - Object principal = authentication.getPrincipal(); - if (principal instanceof Saml2AuthenticatedPrincipal) { - return ((Saml2AuthenticatedPrincipal) principal).getRelyingPartyRegistrationId(); + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + return info.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java index 84a53005aa..975f83c930 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java @@ -24,8 +24,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -130,11 +130,9 @@ final class BaseOpenSamlLogoutRequestValidatorParametersResolver if (registrationId != null) { return registrationId; } - if (authentication == null) { - return null; - } - if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) { - return principal.getRelyingPartyRegistrationId(); + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + return info.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java index 4033e4d453..4fe929e372 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java @@ -46,8 +46,8 @@ import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; @@ -217,12 +217,9 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes if (this.logger.isTraceEnabled()) { this.logger.trace("Attempting to resolve registrationId from " + authentication); } - if (authentication == null) { - return null; - } - Object principal = authentication.getPrincipal(); - if (principal instanceof Saml2AuthenticatedPrincipal) { - return ((Saml2AuthenticatedPrincipal) principal).getRelyingPartyRegistrationId(); + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + return info.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java index a3a22568df..18169605ca 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java @@ -33,8 +33,8 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; @@ -329,11 +329,9 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter { if (registrationId != null) { return registrationId; } - if (authentication == null) { - return null; - } - if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) { - return principal.getRelyingPartyRegistrationId(); + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + return info.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java index aae95e28c7..ce8bc94300 100644 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java +++ b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java @@ -28,8 +28,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -144,11 +144,9 @@ public final class OpenSamlLogoutRequestValidatorParametersResolver if (registrationId != null) { return registrationId; } - if (authentication == null) { - return null; - } - if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) { - return principal.getRelyingPartyRegistrationId(); + Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); + if (info != null) { + return info.getRelyingPartyRegistrationId(); } return null; } From 02a8c416aaaf3f1093477093b65b44795060ec63 Mon Sep 17 00:00:00 2001 From: Christian Schuster Date: Mon, 6 Jun 2022 20:35:46 +0200 Subject: [PATCH 099/149] Add NameID to SAML 2.0 Authentication Info Issue gh-10820 --- .../authentication/Saml2AuthenticatedPrincipal.java | 5 +++++ .../service/authentication/Saml2AuthenticationInfo.java | 7 +++++++ .../logout/BaseOpenSamlLogoutRequestResolver.java | 5 ++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java index 69eb9d7628..80e67376e6 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java @@ -77,6 +77,11 @@ public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal, Sam return null; } + @Override + default String getNameId() { + return getName(); + } + @Override default List getSessionIndexes() { return Collections.emptyList(); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java index 309e2a909b..db412f026e 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java @@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.authentication; import java.util.List; +import org.opensaml.saml.saml2.core.NameID; import org.opensaml.saml.saml2.core.SessionIndex; import org.springframework.security.core.Authentication; @@ -41,6 +42,12 @@ public interface Saml2AuthenticationInfo { */ String getRelyingPartyRegistrationId(); + /** + * Get the {@link NameID} value of the authenticated principal + * @return the {@link NameID} value of the authenticated principal + */ + String getNameId(); + /** * Get the {@link SessionIndex} values of the authenticated principal * @return the {@link SessionIndex} values of the authenticated principal diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java index c4550b632f..1f0e99db32 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java @@ -147,16 +147,19 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol issuer.setValue(entityId); logoutRequest.setIssuer(issuer); NameID nameId = this.nameIdBuilder.buildObject(); - nameId.setValue(authentication.getName()); logoutRequest.setNameID(nameId); Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); if (info != null) { + nameId.setValue(info.getNameId()); for (String index : info.getSessionIndexes()) { SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject(); sessionIndex.setValue(index); logoutRequest.getSessionIndexes().add(sessionIndex); } } + else { + nameId.setValue(authentication.getName()); + } logoutRequest.setIssueInstant(Instant.now(this.clock)); this.parametersConsumer .accept(new LogoutRequestParameters(request, registration, authentication, logoutRequest)); From 9b724377ce9210a2b8cef13f3fa545284a4fcbec Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 6 Jun 2025 14:24:12 -0600 Subject: [PATCH 100/149] Rework Saml2 Authentication Statement This commit separates the authentication principal, the assertion details, and the relying party tenant into separate components. This allows the principal to be completely decoupled from how Spring Security triggers and processes SLO. Specifically, it adds Saml2AssertionAuthentication, a new authentication implementation that allows an Object principal and a Saml2ResponseAssertionAccessor credential. It also moves the relying party registration id from Saml2AuthenticatedPrincipal to Saml2AssertionAuthentication. As such, Saml2AuthenticatedPrincipal is now deprecated in favor of placing its assertion components in Saml2ResponseAssertionAccessor and the relying party registration id in Saml2AssertionAuthentication. Closes gh-10820 --- .../saml2/Saml2LogoutConfigurer.java | 15 ++- .../http/Saml2LogoutBeanDefinitionParser.java | 15 ++- .../security/SerializationSamples.java | 15 ++- ...on.Saml2AssertionAuthentication.serialized | Bin 0 -> 1386 bytes ...tication.Saml2ResponseAssertion.serialized | Bin 0 -> 350 bytes .../ROOT/pages/migration/servlet/saml2.adoc | 54 ++++++++ .../servlet/saml2/login/authentication.adoc | 8 +- .../Saml2AssertionAuthenticationMixin.java | 59 +++++++++ .../saml2/jackson2/Saml2Jackson2Module.java | 4 + ...leSaml2ResponseAssertionAccessorMixin.java | 56 +++++++++ .../DefaultSaml2AuthenticatedPrincipal.java | 8 ++ .../Saml2AssertionAuthentication.java | 65 ++++++++++ .../Saml2AuthenticatedPrincipal.java | 13 +- .../authentication/Saml2Authentication.java | 10 +- .../Saml2AuthenticationInfo.java | 85 ------------- .../Saml2ResponseAssertion.java | 115 ++++++++++++++++++ .../Saml2ResponseAssertionAccessor.java | 65 ++++++++++ .../BaseOpenSamlLogoutRequestValidator.java | 6 +- .../BaseOpenSamlLogoutRequestResolver.java | 31 +++-- ...outRequestValidatorParametersResolver.java | 14 ++- .../BaseOpenSamlLogoutResponseResolver.java | 14 ++- .../logout/Saml2LogoutRequestFilter.java | 14 ++- ...outRequestValidatorParametersResolver.java | 10 +- .../OpenSaml5AuthenticationProvider.java | 15 +-- ...faultSaml2AuthenticatedPrincipalTests.java | 3 +- 25 files changed, 558 insertions(+), 136 deletions(-) create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication.serialized create mode 100644 config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion.serialized create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2AssertionAuthenticationMixin.java create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/SimpleSaml2ResponseAssertionAccessorMixin.java create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java delete mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertion.java create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertionAccessor.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java index f8303a81b9..fc9950d964 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java @@ -33,7 +33,9 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator; @@ -531,7 +533,16 @@ public final class Saml2LogoutConfigurer> @Override public boolean matches(HttpServletRequest request) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - return Saml2AuthenticationInfo.fromAuthentication(authentication) != null; + if (authentication == null) { + return false; + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal) { + return true; + } + if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor) { + return true; + } + return authentication instanceof Saml2Authentication; } } diff --git a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java index dfeed0d981..749127027c 100644 --- a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java @@ -31,7 +31,9 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter; @@ -236,7 +238,16 @@ final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser { @Override public boolean matches(HttpServletRequest request) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - return Saml2AuthenticationInfo.fromAuthentication(authentication) != null; + if (authentication == null) { + return false; + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal) { + return true; + } + if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor) { + return true; + } + return authentication instanceof Saml2Authentication; } public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { diff --git a/config/src/test/java/org/springframework/security/SerializationSamples.java b/config/src/test/java/org/springframework/security/SerializationSamples.java index 396828ca33..d120a6ebd0 100644 --- a/config/src/test/java/org/springframework/security/SerializationSamples.java +++ b/config/src/test/java/org/springframework/security/SerializationSamples.java @@ -170,11 +170,14 @@ import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.credentials.TestSaml2X509Credentials; import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; import org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationTokens; import org.springframework.security.saml2.provider.service.authentication.TestSaml2Authentications; import org.springframework.security.saml2.provider.service.authentication.TestSaml2LogoutRequests; @@ -520,8 +523,16 @@ final class SerializationSamples { generatorByClassName.put(Saml2Exception.class, (r) -> new Saml2Exception("message", new IOException("fail"))); generatorByClassName.put(DefaultSaml2AuthenticatedPrincipal.class, (r) -> TestSaml2Authentications.authentication().getPrincipal()); - generatorByClassName.put(Saml2Authentication.class, - (r) -> applyDetails(TestSaml2Authentications.authentication())); + Saml2Authentication saml2 = TestSaml2Authentications.authentication(); + generatorByClassName.put(Saml2Authentication.class, (r) -> applyDetails(saml2)); + Saml2ResponseAssertionAccessor assertion = Saml2ResponseAssertion.withResponseValue("response") + .nameId("name") + .sessionIndexes(List.of("id")) + .attributes(Map.of("key", List.of("value"))) + .build(); + generatorByClassName.put(Saml2ResponseAssertion.class, (r) -> assertion); + generatorByClassName.put(Saml2AssertionAuthentication.class, (r) -> applyDetails( + new Saml2AssertionAuthentication(assertion, authentication.getAuthorities(), "id"))); generatorByClassName.put(Saml2PostAuthenticationRequest.class, (r) -> TestSaml2PostAuthenticationRequests.create()); generatorByClassName.put(Saml2RedirectAuthenticationRequest.class, diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication.serialized new file mode 100644 index 0000000000000000000000000000000000000000..61898671f849a92212fc89c67a7c8b666bbacabc GIT binary patch literal 1386 zcmb_cJ#Q015FMYLfI-d&NPLQj0uU1DPEeA{AyJ5QQIJg%K|!>>o0v`3cgOD9Ia5UN z2ht)68Y%=SfnNYZf&vL7L<7G73PeFe!R%ff$3Zd_TyeVFot^jQy_wzo1_MgMQY`DP zN+gf!%aS#4EtadU!k{U+Zo7&##6>reaf^p2XfG%t5TNr5`FwNu0t(vH^qFwV_ z?%90x=H-{lkMHLkaC|7TPLPJcCuDCIve!ZOEV9=@rge+#^&)!~xq>Q*BZXZCL4Zod za@>bw5=EP8yUL_)SFp~NmX`EPsNsOW!dlD|EUJ4|Z6p{^B`o|mMS6r7cvO5h8~L9z zA&lSwPnhUsF;`o`Kojqht-n**ZCD3~s7?2!)~k(Mu9;c`-RsA-com}$PtLAK)8UW< z`5RE$aSlTt_M88h?B}R79Lh9mYA(FXu@FR$JSK*FLez{4Ww;g6BuBk;#>nZKP`4YZ zPR&P+IONNm)dc!vz^~!c7oLCJ7;zxyLqX7rhLIgGCdL?X{_p9!Fh?ig?%SI`$86GE zU|0>N36VP3%~F<{@c~iY2)nyz5i)w zZt~g3O$Q1->|>f{uh!J2-G+Dc!7NLt&jus9mA1Irin3u}V4|5R>cdcnbrfQXw$7sU zezfbo>69wU#f_&gE**b7YMsw(R=39It?`bA{3^EX`6Hy`Ry$}IXc@3H)le{GH0&ig U?UnOqtiwZV>X;Gtuz8*Q4Sh!7+5i9m literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion.serialized b/config/src/test/resources/serialized/7.0.x/org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion.serialized new file mode 100644 index 0000000000000000000000000000000000000000..0bc93ea8af543fd06d7917c4d55672a86718d464 GIT binary patch literal 350 zcmZXPO-jT-5QQs0BO*9DZoNYXTskY!O&}r|aB$fsWo#$YX}clFlNDIP_< zgmwTt6G1+h%8)owh?5k(#^G%3tV<~E(ojMDqabai}pYm+YPEl>vcP* z3We z(o753&>o%F413?BR_Z1z{f2J*bld)9U&yw{JGh6qfH5JF$K<+37gBfsq!x;J>Wu$y z5>O(b#BtZQCED4;^XuXM(` details from the principal. +This allows Spring Security to retrieve needed assertion details to perform Single Logout. + +This deprecates `Saml2AuthenticatedPrincipal`. +You no longer need to implement it to use `Saml2Authentication`. + +Instead, the credential implements `Saml2ResponseAssertionAccessor`, which Spring Security 7 favors when determining the appropriate action based on the authentication. + +This change is made automatically for you when using the defaults. + +If this causes you trouble when upgrading, you can publish a custom `ResponseAuhenticationConverter` to return a `Saml2Authentication` instead of returning a `Saml2AssertionAuthentication` like so: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +OpenSaml5AuthenticationProvider authenticationProvider() { + OpenSaml5AuthenticationProvider authenticationProvider = + new OpenSaml5AuthenticationProvider(); + ResponseAuthenticationConverter defaults = new ResponseAuthenticationConverter(); + authenticationProvider.setResponseAuthenticationConverter( + defaults.andThen((authentication) -> new Saml2Authentication( + authentication.getPrincipal(), + authentication.getSaml2Response(), + authentication.getAuthorities()))); + return authenticationProvider; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun authenticationProvider(): OpenSaml5AuthenticationProvider { + val authenticationProvider = OpenSaml5AuthenticationProvider() + val defaults = ResponseAuthenticationConverter() + authenticationProvider.setResponseAuthenticationConverter( + defaults.andThen { authentication -> + Saml2Authentication(authentication.getPrincipal(), + authentication.getSaml2Response(), + authentication.getAuthorities()) + }) + return authenticationProvider +} +---- +====== + +If you are constructing a `Saml2Authentication` instance yourself, consider changing to `Saml2AssertionAuthentication` to get the same benefit as the current default. diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc index 1c517914dc..400a66ad3a 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc @@ -341,8 +341,10 @@ class MyUserDetailsResponseAuthenticationConverter implements Converter UserDetails principal = this.userDetailsService.loadByUsername(username); <2> String saml2Response = authentication.getSaml2Response(); + Saml2ResponseAssertionAccessor assertion = new OpenSamlResponseAssertionAccessor( + saml2Response, CollectionUtils.getFirst(response.getAssertions())); Collection authorities = principal.getAuthorities(); - return new Saml2Authentication((AuthenticatedPrincipal) userDetails, saml2Response, authorities); <3> + return new Saml2AssertionAuthentication(userDetails, assertion, authorities); <3> } } @@ -361,8 +363,10 @@ open class MyUserDetailsResponseAuthenticationConverter(val delegate: ResponseAu val authentication = this.delegate.convert(responseToken) <1> val principal = this.userDetailsService.loadByUsername(username) <2> val saml2Response = authentication.getSaml2Response() + val assertion = OpenSamlResponseAssertionAccessor( + saml2Response, CollectionUtils.getFirst(response.getAssertions())) val authorities = principal.getAuthorities() - return Saml2Authentication(userDetails as AuthenticatedPrincipal, saml2Response, authorities) <3> + return Saml2AssertionAuthentication(userDetails, assertion, authorities) <3> } } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2AssertionAuthenticationMixin.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2AssertionAuthenticationMixin.java new file mode 100644 index 0000000000..d3d3156446 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2AssertionAuthenticationMixin.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.jackson2; + +import java.util.Collection; + +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.GrantedAuthority; +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; + +/** + * Jackson Mixin class helps in serialize/deserialize + * {@link Saml2AssertionAuthentication}. + * + *

+ *     ObjectMapper mapper = new ObjectMapper();
+ *     mapper.registerModule(new Saml2Jackson2Module());
+ * 
+ * + * @author Josh Cummings + * @since 7.0 + * @see Saml2Jackson2Module + * @see SecurityJackson2Modules + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(value = { "authenticated" }, ignoreUnknown = true) +class Saml2AssertionAuthenticationMixin { + + @JsonCreator + Saml2AssertionAuthenticationMixin(@JsonProperty("principal") Object principal, + @JsonProperty("assertion") Saml2ResponseAssertionAccessor assertion, + @JsonProperty("authorities") Collection authorities, + @JsonProperty("relyingPartyRegistrationId") String relyingPartyRegistrationId) { + } + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2Jackson2Module.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2Jackson2Module.java index 3d99fc2cfa..b53fcd15bc 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2Jackson2Module.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/Saml2Jackson2Module.java @@ -22,10 +22,12 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.security.jackson2.SecurityJackson2Modules; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; /** @@ -49,6 +51,8 @@ public class Saml2Jackson2Module extends SimpleModule { @Override public void setupModule(SetupContext context) { context.setMixInAnnotations(Saml2Authentication.class, Saml2AuthenticationMixin.class); + context.setMixInAnnotations(Saml2AssertionAuthentication.class, Saml2AssertionAuthenticationMixin.class); + context.setMixInAnnotations(Saml2ResponseAssertion.class, SimpleSaml2ResponseAssertionAccessorMixin.class); context.setMixInAnnotations(DefaultSaml2AuthenticatedPrincipal.class, DefaultSaml2AuthenticatedPrincipalMixin.class); context.setMixInAnnotations(Saml2LogoutRequest.class, Saml2LogoutRequestMixin.class); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/SimpleSaml2ResponseAssertionAccessorMixin.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/SimpleSaml2ResponseAssertionAccessorMixin.java new file mode 100644 index 0000000000..7f4d02b4a0 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/jackson2/SimpleSaml2ResponseAssertionAccessorMixin.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.jackson2; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion; + +/** + * Jackson Mixin class helps in serialize/deserialize {@link Saml2ResponseAssertion}. + * + *
+ *     ObjectMapper mapper = new ObjectMapper();
+ *     mapper.registerModule(new Saml2Jackson2Module());
+ * 
+ * + * @author Josh Cummings + * @since 7.0 + * @see Saml2Jackson2Module + * @see SecurityJackson2Modules + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(value = { "authenticated" }, ignoreUnknown = true) +class SimpleSaml2ResponseAssertionAccessorMixin { + + @JsonCreator + SimpleSaml2ResponseAssertionAccessorMixin(@JsonProperty("responseValue") String responseValue, + @JsonProperty("nameId") String nameId, @JsonProperty("sessionIndexes") List sessionIndexes, + @JsonProperty("attributes") Map> attributes) { + } + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java index d99f640cb5..d2e9658fd0 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java @@ -30,7 +30,9 @@ import org.springframework.util.Assert; * * @author Clement Stoquart * @since 5.4 + * @deprecated Please use {@link Saml2ResponseAssertionAccessor} */ +@Deprecated public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPrincipal, Serializable { @Serial @@ -58,6 +60,12 @@ public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPri this.sessionIndexes = sessionIndexes; } + public DefaultSaml2AuthenticatedPrincipal(String name, Saml2ResponseAssertionAccessor assertion) { + this.name = name; + this.attributes = assertion.getAttributes(); + this.sessionIndexes = assertion.getSessionIndexes(); + } + @Override public String getName() { return this.name; diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java new file mode 100644 index 0000000000..c38cfbfbd1 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.authentication; + +import java.io.Serial; +import java.util.Collection; + +import org.springframework.security.core.GrantedAuthority; + +/** + * An authentication based off of a SAML 2.0 Assertion + * + * @author Josh Cummings + * @since 7.0 + * @see Saml2ResponseAssertionAccessor + * @see Saml2ResponseAssertion + */ +public class Saml2AssertionAuthentication extends Saml2Authentication { + + @Serial + private static final long serialVersionUID = -4194323643788693205L; + + private final Saml2ResponseAssertionAccessor assertion; + + private final String relyingPartyRegistrationId; + + public Saml2AssertionAuthentication(Saml2ResponseAssertionAccessor assertion, + Collection authorities, String relyingPartyRegistrationId) { + super(assertion, assertion.getResponseValue(), authorities); + this.assertion = assertion; + this.relyingPartyRegistrationId = relyingPartyRegistrationId; + } + + public Saml2AssertionAuthentication(Object principal, Saml2ResponseAssertionAccessor assertion, + Collection authorities, String relyingPartyRegistrationId) { + super(principal, assertion.getResponseValue(), authorities); + this.assertion = assertion; + this.relyingPartyRegistrationId = relyingPartyRegistrationId; + setAuthenticated(true); + } + + @Override + public Saml2ResponseAssertionAccessor getCredentials() { + return this.assertion; + } + + public String getRelyingPartyRegistrationId() { + return this.relyingPartyRegistrationId; + } + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java index 80e67376e6..6f3e32c888 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java @@ -30,8 +30,12 @@ import org.springframework.util.CollectionUtils; * * @author Clement Stoquart * @since 5.2.2 + * @deprecated Please use + * {@link Saml2AssertionAuthentication#getRelyingPartyRegistrationId()} and + * {@link Saml2ResponseAssertionAccessor} instead */ -public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal, Saml2AuthenticationInfo { +@Deprecated +public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal { /** * Get the first value of Saml2 token attribute by name @@ -72,17 +76,10 @@ public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal, Sam * @return the {@link RelyingPartyRegistration} identifier * @since 5.6 */ - @Override default String getRelyingPartyRegistrationId() { return null; } - @Override - default String getNameId() { - return getName(); - } - - @Override default List getSessionIndexes() { return Collections.emptyList(); } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java index 2292f52a37..e628f260e8 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java @@ -41,7 +41,7 @@ public class Saml2Authentication extends AbstractAuthenticationToken { @Serial private static final long serialVersionUID = 405897702378720477L; - private final AuthenticatedPrincipal principal; + private final Object principal; private final String saml2Response; @@ -61,6 +61,14 @@ public class Saml2Authentication extends AbstractAuthenticationToken { setAuthenticated(true); } + public Saml2Authentication(Object principal, String saml2Response, + Collection authorities) { + super(authorities); + this.principal = principal; + this.saml2Response = saml2Response; + setAuthenticated(true); + } + @Override public Object getPrincipal() { return this.principal; diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java deleted file mode 100644 index db412f026e..0000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationInfo.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.saml2.provider.service.authentication; - -import java.util.List; - -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.SessionIndex; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; - -/** - * Additional SAML 2.0 authentication information - * - *

- * SAML 2.0 Single Logout requires that the {@link Authentication#getPrincipal() - * authenticated principal} or the {@link Authentication} itself implements this - * interface. - * - * @author Christian Schuster - */ -public interface Saml2AuthenticationInfo { - - /** - * Get the {@link RelyingPartyRegistration} identifier - * @return the {@link RelyingPartyRegistration} identifier - */ - String getRelyingPartyRegistrationId(); - - /** - * Get the {@link NameID} value of the authenticated principal - * @return the {@link NameID} value of the authenticated principal - */ - String getNameId(); - - /** - * Get the {@link SessionIndex} values of the authenticated principal - * @return the {@link SessionIndex} values of the authenticated principal - */ - List getSessionIndexes(); - - /** - * Try to obtain a {@link Saml2AuthenticationInfo} instance from an - * {@link Authentication} - * - *

- * The result is either the {@link Authentication#getPrincipal() authenticated - * principal}, the {@link Authentication} itself, or {@code null}. - * - *

- * Returning {@code null} indicates that the given {@link Authentication} does not - * represent a SAML 2.0 authentication. - * @param authentication the {@link Authentication} - * @return the {@link Saml2AuthenticationInfo} or {@code null} if unavailable - */ - static Saml2AuthenticationInfo fromAuthentication(Authentication authentication) { - if (authentication == null) { - return null; - } - Object principal = authentication.getPrincipal(); - if (principal instanceof Saml2AuthenticationInfo) { - return (Saml2AuthenticationInfo) principal; - } - if (authentication instanceof Saml2AuthenticationInfo) { - return (Saml2AuthenticationInfo) authentication; - } - return null; - } - -} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertion.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertion.java new file mode 100644 index 0000000000..ea5dd2a8a9 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertion.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.authentication; + +import java.io.Serial; +import java.util.List; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * An OpenSAML-based implementation of {@link Saml2ResponseAssertionAccessor} + * + * @author Josh Cummings + * @since 7.0 + */ +public class Saml2ResponseAssertion implements Saml2ResponseAssertionAccessor { + + @Serial + private static final long serialVersionUID = -7505233045395024212L; + + private final String responseValue; + + private final String nameId; + + private final List sessionIndexes; + + private final Map> attributes; + + Saml2ResponseAssertion(String responseValue, String nameId, List sessionIndexes, + Map> attributes) { + Assert.notNull(responseValue, "response value cannot be null"); + Assert.notNull(nameId, "nameId cannot be null"); + Assert.notNull(sessionIndexes, "sessionIndexes cannot be null"); + Assert.notNull(attributes, "attributes cannot be null"); + this.responseValue = responseValue; + this.nameId = nameId; + this.sessionIndexes = sessionIndexes; + this.attributes = attributes; + } + + public static Builder withResponseValue(String responseValue) { + return new Builder(responseValue); + } + + @Override + public String getNameId() { + return this.nameId; + } + + @Override + public List getSessionIndexes() { + return this.sessionIndexes; + } + + @Override + public Map> getAttributes() { + return this.attributes; + } + + @Override + public String getResponseValue() { + return this.responseValue; + } + + public static final class Builder { + + private final String responseValue; + + private String nameId; + + private List sessionIndexes = List.of(); + + private Map> attributes = Map.of(); + + Builder(String responseValue) { + this.responseValue = responseValue; + } + + public Builder nameId(String nameId) { + this.nameId = nameId; + return this; + } + + public Builder sessionIndexes(List sessionIndexes) { + this.sessionIndexes = sessionIndexes; + return this; + } + + public Builder attributes(Map> attributes) { + this.attributes = attributes; + return this; + } + + public Saml2ResponseAssertion build() { + return new Saml2ResponseAssertion(this.responseValue, this.nameId, this.sessionIndexes, this.attributes); + } + + } + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertionAccessor.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertionAccessor.java new file mode 100644 index 0000000000..f137b79848 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ResponseAssertionAccessor.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.authentication; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.jspecify.annotations.Nullable; + +import org.springframework.util.CollectionUtils; + +/** + * An interface that represents key details from a SAML 2.0 Assertion + * + * @author Josh Cummings + * @since 7.0 + * @see Saml2ResponseAssertion + */ +public interface Saml2ResponseAssertionAccessor extends Serializable { + + String getNameId(); + + List getSessionIndexes(); + + /** + * Get the first value of Saml2 token attribute by name + * @param name the name of the attribute + * @param the type of the attribute + * @return the first attribute value or {@code null} otherwise + */ + @Nullable default A getFirstAttribute(String name) { + List values = getAttribute(name); + return CollectionUtils.firstElement(values); + } + + /** + * Get the Saml2 token attribute by name + * @param name the name of the attribute + * @param the type of the attribute + * @return the attribute or {@code null} otherwise + */ + @Nullable default List getAttribute(String name) { + return (List) getAttributes().get(name); + } + + Map> getAttributes(); + + String getResponseValue(); + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java index b96cb947c6..9165113684 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java @@ -27,6 +27,7 @@ import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.VerificationConfigurer; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.VerificationConfigurer.RedirectParameters; import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; @@ -142,8 +143,9 @@ class BaseOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator } private void validateNameId(NameID nameId, Authentication authentication, Collection errors) { - String name = nameId.getValue(); - if (!name.equals(authentication.getName())) { + String name = (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor assertion) + ? assertion.getNameId() : authentication.getName(); + if (!nameId.getValue().equals(name)) { errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST, "Failed to match subject in LogoutRequest with currently logged in user")); } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java index 1f0e99db32..def1ab7afe 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestResolver.java @@ -42,7 +42,9 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; @@ -148,17 +150,25 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol logoutRequest.setIssuer(issuer); NameID nameId = this.nameIdBuilder.buildObject(); logoutRequest.setNameID(nameId); - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { + if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor info) { nameId.setValue(info.getNameId()); + } + else { + nameId.setValue(authentication.getName()); + } + if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor info) { for (String index : info.getSessionIndexes()) { SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject(); sessionIndex.setValue(index); logoutRequest.getSessionIndexes().add(sessionIndex); } } - else { - nameId.setValue(authentication.getName()); + else if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal info) { + for (String index : info.getSessionIndexes()) { + SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject(); + sessionIndex.setValue(index); + logoutRequest.getSessionIndexes().add(sessionIndex); + } } logoutRequest.setIssueInstant(Instant.now(this.clock)); this.parametersConsumer @@ -194,9 +204,14 @@ final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResol if (this.logger.isTraceEnabled()) { this.logger.trace("Attempting to resolve registrationId from " + authentication); } - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { - return info.getRelyingPartyRegistrationId(); + if (authentication == null) { + return null; + } + if (authentication instanceof Saml2AssertionAuthentication response) { + return response.getRelyingPartyRegistrationId(); + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) { + return principal.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java index 975f83c930..3fc54e88bf 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutRequestValidatorParametersResolver.java @@ -24,8 +24,9 @@ import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -130,9 +131,14 @@ final class BaseOpenSamlLogoutRequestValidatorParametersResolver if (registrationId != null) { return registrationId; } - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { - return info.getRelyingPartyRegistrationId(); + if (authentication == null) { + return null; + } + if (authentication instanceof Saml2AssertionAuthentication saml2) { + return saml2.getRelyingPartyRegistrationId(); + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal saml2) { + return saml2.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java index 4fe929e372..48dd0bc47a 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/BaseOpenSamlLogoutResponseResolver.java @@ -46,8 +46,9 @@ import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; @@ -217,9 +218,14 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes if (this.logger.isTraceEnabled()) { this.logger.trace("Attempting to resolve registrationId from " + authentication); } - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { - return info.getRelyingPartyRegistrationId(); + if (authentication == null) { + return null; + } + if (authentication instanceof Saml2AssertionAuthentication saml2) { + return saml2.getRelyingPartyRegistrationId(); + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal saml2) { + return saml2.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java index 18169605ca..29fe0921c5 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java @@ -33,8 +33,9 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; @@ -329,9 +330,14 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter { if (registrationId != null) { return registrationId; } - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { - return info.getRelyingPartyRegistrationId(); + if (authentication == null) { + return null; + } + if (authentication instanceof Saml2AssertionAuthentication saml2) { + return saml2.getRelyingPartyRegistrationId(); + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal saml2) { + return saml2.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java index ce8bc94300..aae95e28c7 100644 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java +++ b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java @@ -28,8 +28,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationInfo; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -144,9 +144,11 @@ public final class OpenSamlLogoutRequestValidatorParametersResolver if (registrationId != null) { return registrationId; } - Saml2AuthenticationInfo info = Saml2AuthenticationInfo.fromAuthentication(authentication); - if (info != null) { - return info.getRelyingPartyRegistrationId(); + if (authentication == null) { + return null; + } + if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) { + return principal.getRelyingPartyRegistrationId(); } return null; } diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java index d2dbb5ba0c..6d49c6ef2f 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java @@ -893,14 +893,15 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv Saml2AuthenticationToken token = responseToken.token; Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); String username = this.principalNameConverter.convert(assertion); - Map> attributes = BaseOpenSamlAuthenticationProvider.getAssertionAttributes(assertion); - List sessionIndexes = BaseOpenSamlAuthenticationProvider.getSessionIndexes(assertion); - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes, - sessionIndexes); String registrationId = responseToken.token.getRelyingPartyRegistration().getRegistrationId(); - principal.setRelyingPartyRegistrationId(registrationId); - return new Saml2Authentication(principal, token.getSaml2Response(), - this.grantedAuthoritiesConverter.convert(assertion)); + Saml2ResponseAssertionAccessor accessor = Saml2ResponseAssertion.withResponseValue(token.getSaml2Response()) + .nameId(authenticatedPrincipal(assertion)) + .sessionIndexes(BaseOpenSamlAuthenticationProvider.getSessionIndexes(assertion)) + .attributes(BaseOpenSamlAuthenticationProvider.getAssertionAttributes(assertion)) + .build(); + Saml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, accessor); + Collection authorities = this.grantedAuthoritiesConverter.convert(assertion); + return new Saml2AssertionAuthentication(principal, accessor, authorities, registrationId); } /** diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java index 4ea06059a6..75ab69d108 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java @@ -48,7 +48,8 @@ public class DefaultSaml2AuthenticatedPrincipalTests { @Test public void createDefaultSaml2AuthenticatedPrincipalWhenAttributesNullThenException() { - assertThatIllegalArgumentException().isThrownBy(() -> new DefaultSaml2AuthenticatedPrincipal("user", null)) + assertThatIllegalArgumentException() + .isThrownBy(() -> new DefaultSaml2AuthenticatedPrincipal("user", (Map>) null)) .withMessageContaining("attributes cannot be null"); } From 220f49d86e83850c6f72678b34f4e941e811e9e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 03:14:31 +0000 Subject: [PATCH 101/149] Bump io.projectreactor:reactor-bom from 2025.0.0-M3 to 2025.0.0-M4 Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2025.0.0-M3 to 2025.0.0-M4. - [Release notes](https://github.com/reactor/reactor/releases) - [Commits](https://github.com/reactor/reactor/compare/2025.0.0-M3...2025.0.0-M4) --- updated-dependencies: - dependency-name: io.projectreactor:reactor-bom dependency-version: 2025.0.0-M4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2125a073ab..fc72627f88 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,7 +31,7 @@ commons-collections = "commons-collections:commons-collections:3.2.2" io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3" io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.8" io-mockk = "io.mockk:mockk:1.14.2" -io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-M3" +io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-M4" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" } io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" } From 60f729156b7853f47a08e18305871aab34f425ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 03:15:08 +0000 Subject: [PATCH 102/149] Bump org.hibernate.orm:hibernate-core from 7.0.0.Final to 7.0.1.Final Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.0.0.Final to 7.0.1.Final. - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.0.1/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.0.0...7.0.1) --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 7.0.1.Final dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2125a073ab..18e7c2c9bd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,7 +71,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", 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:7.0.0.Final" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.0.1.Final" org-hsqldb = "org.hsqldb:hsqldb:2.7.4" 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.25" From 16fd24c0025f2c1d77eafd59ddd59a0a42730e81 Mon Sep 17 00:00:00 2001 From: "chao.wang" Date: Thu, 8 May 2025 17:24:30 +0800 Subject: [PATCH 103/149] Add JdbcAssertingPartyMetadataRepository Closes gh-16012 Signed-off-by: chao.wang --- ...ing-security-saml2-service-provider.gradle | 2 + .../JdbcAssertingPartyMetadataRepository.java | 190 ++++++++++++++++++ ...serting-party-metadata-schema-postgres.sql | 14 ++ .../saml2-asserting-party-metadata-schema.sql | 14 ++ ...AssertingPartyMetadataRepositoryTests.java | 177 ++++++++++++++++ .../src/test/resources/rsa.crt | 23 +++ 6 files changed, 420 insertions(+) create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java create mode 100644 saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema-postgres.sql create mode 100644 saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql create mode 100644 saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java create mode 100644 saml2/saml2-service-provider/src/test/resources/rsa.crt diff --git a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle index 8b40d415d6..b05c1bbd57 100644 --- a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle +++ b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle @@ -106,6 +106,7 @@ dependencies { provided 'jakarta.servlet:jakarta.servlet-api' optional 'com.fasterxml.jackson.core:jackson-databind' + optional 'org.springframework:spring-jdbc' testImplementation 'com.squareup.okhttp3:mockwebserver' testImplementation "org.assertj:assertj-core" @@ -118,6 +119,7 @@ dependencies { testImplementation "org.springframework:spring-test" testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testRuntimeOnly 'org.hsqldb:hsqldb' } jar { diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java new file mode 100644 index 0000000000..620e6bdf2e --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java @@ -0,0 +1,190 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.registration; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.log.LogMessage; +import org.springframework.core.serializer.DefaultDeserializer; +import org.springframework.core.serializer.Deserializer; +import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.SqlParameterValue; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails; +import org.springframework.util.Assert; + +/** + * A JDBC implementation of {@link AssertingPartyMetadataRepository}. + * + * @author Cathy Wang + * @since 7.0 + */ +public final class JdbcAssertingPartyMetadataRepository implements AssertingPartyMetadataRepository { + + private final JdbcOperations jdbcOperations; + + private RowMapper assertingPartyMetadataRowMapper = new AssertingPartyMetadataRowMapper( + ResultSet::getBytes); + + // @formatter:off + static final String COLUMN_NAMES = "entity_id, " + + "singlesignon_url, " + + "singlesignon_binding, " + + "singlesignon_sign_request, " + + "signing_algorithms, " + + "verification_credentials, " + + "encryption_credentials, " + + "singlelogout_url, " + + "singlelogout_response_url, " + + "singlelogout_binding"; + // @formatter:on + + private static final String TABLE_NAME = "saml2_asserting_party_metadata"; + + private static final String ENTITY_ID_FILTER = "entity_id = ?"; + + // @formatter:off + private static final String LOAD_BY_ID_SQL = "SELECT " + COLUMN_NAMES + + " FROM " + TABLE_NAME + + " WHERE " + ENTITY_ID_FILTER; + + private static final String LOAD_ALL_SQL = "SELECT " + COLUMN_NAMES + + " FROM " + TABLE_NAME; + // @formatter:on + + /** + * Constructs a {@code JdbcRelyingPartyRegistrationRepository} using the provided + * parameters. + * @param jdbcOperations the JDBC operations + */ + public JdbcAssertingPartyMetadataRepository(JdbcOperations jdbcOperations) { + Assert.notNull(jdbcOperations, "jdbcOperations cannot be null"); + this.jdbcOperations = jdbcOperations; + } + + /** + * Sets the {@link RowMapper} used for mapping the current row in + * {@code java.sql.ResultSet} to {@link AssertingPartyMetadata}. The default is + * {@link AssertingPartyMetadataRowMapper}. + * @param assertingPartyMetadataRowMapper the {@link RowMapper} used for mapping the + * current row in {@code java.sql.ResultSet} to {@link AssertingPartyMetadata} + */ + public void setAssertingPartyMetadataRowMapper(RowMapper assertingPartyMetadataRowMapper) { + Assert.notNull(assertingPartyMetadataRowMapper, "assertingPartyMetadataRowMapper cannot be null"); + this.assertingPartyMetadataRowMapper = assertingPartyMetadataRowMapper; + } + + @Override + public AssertingPartyMetadata findByEntityId(String entityId) { + Assert.hasText(entityId, "entityId cannot be empty"); + SqlParameterValue[] parameters = new SqlParameterValue[] { new SqlParameterValue(Types.VARCHAR, entityId) }; + PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); + List result = this.jdbcOperations.query(LOAD_BY_ID_SQL, pss, + this.assertingPartyMetadataRowMapper); + return !result.isEmpty() ? result.get(0) : null; + } + + @Override + public Iterator iterator() { + List result = this.jdbcOperations.query(LOAD_ALL_SQL, + this.assertingPartyMetadataRowMapper); + return result.iterator(); + } + + /** + * The default {@link RowMapper} that maps the current row in + * {@code java.sql.ResultSet} to {@link AssertingPartyMetadata}. + */ + private static final class AssertingPartyMetadataRowMapper implements RowMapper { + + private final Log logger = LogFactory.getLog(AssertingPartyMetadataRowMapper.class); + + private final Deserializer deserializer = new DefaultDeserializer(); + + private final GetBytes getBytes; + + AssertingPartyMetadataRowMapper(GetBytes getBytes) { + this.getBytes = getBytes; + } + + @Override + public AssertingPartyMetadata mapRow(ResultSet rs, int rowNum) throws SQLException { + String entityId = rs.getString("entity_id"); + String singleSignOnUrl = rs.getString("singlesignon_url"); + Saml2MessageBinding singleSignOnBinding = Saml2MessageBinding.from(rs.getString("singlesignon_binding")); + boolean singleSignOnSignRequest = rs.getBoolean("singlesignon_sign_request"); + String singleLogoutUrl = rs.getString("singlelogout_url"); + String singleLogoutResponseUrl = rs.getString("singlelogout_response_url"); + Saml2MessageBinding singleLogoutBinding = Saml2MessageBinding.from(rs.getString("singlelogout_binding")); + byte[] signingAlgorithmsBytes = this.getBytes.getBytes(rs, "signing_algorithms"); + byte[] verificationCredentialsBytes = this.getBytes.getBytes(rs, "verification_credentials"); + byte[] encryptionCredentialsBytes = this.getBytes.getBytes(rs, "encryption_credentials"); + + AssertingPartyMetadata.Builder builder = new AssertingPartyDetails.Builder(); + try { + if (signingAlgorithmsBytes != null) { + List signingAlgorithms = (List) this.deserializer + .deserializeFromByteArray(signingAlgorithmsBytes); + builder.signingAlgorithms((algorithms) -> algorithms.addAll(signingAlgorithms)); + } + if (verificationCredentialsBytes != null) { + Collection verificationCredentials = (Collection) this.deserializer + .deserializeFromByteArray(verificationCredentialsBytes); + builder.verificationX509Credentials((credentials) -> credentials.addAll(verificationCredentials)); + } + if (encryptionCredentialsBytes != null) { + Collection encryptionCredentials = (Collection) this.deserializer + .deserializeFromByteArray(encryptionCredentialsBytes); + builder.encryptionX509Credentials((credentials) -> credentials.addAll(encryptionCredentials)); + } + } + catch (Exception ex) { + this.logger.debug(LogMessage.format("Parsing serialized credentials for entity %s failed", entityId), + ex); + return null; + } + + builder.entityId(entityId) + .wantAuthnRequestsSigned(singleSignOnSignRequest) + .singleSignOnServiceLocation(singleSignOnUrl) + .singleSignOnServiceBinding(singleSignOnBinding) + .singleLogoutServiceLocation(singleLogoutUrl) + .singleLogoutServiceBinding(singleLogoutBinding) + .singleLogoutServiceResponseLocation(singleLogoutResponseUrl); + return builder.build(); + } + + } + + private interface GetBytes { + + byte[] getBytes(ResultSet rs, String columnName) throws SQLException; + + } + +} diff --git a/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema-postgres.sql b/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema-postgres.sql new file mode 100644 index 0000000000..ffa047fe7b --- /dev/null +++ b/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema-postgres.sql @@ -0,0 +1,14 @@ +CREATE TABLE saml2_asserting_party_metadata +( + entity_id VARCHAR(1000) NOT NULL, + singlesignon_url VARCHAR(1000) NOT NULL, + singlesignon_binding VARCHAR(100), + singlesignon_sign_request boolean, + signing_algorithms BYTEA, + verification_credentials BYTEA NOT NULL, + encryption_credentials BYTEA, + singlelogout_url VARCHAR(1000), + singlelogout_response_url VARCHAR(1000), + singlelogout_binding VARCHAR(100), + PRIMARY KEY (entity_id) +); diff --git a/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql b/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql new file mode 100644 index 0000000000..2fd6cb8cdf --- /dev/null +++ b/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql @@ -0,0 +1,14 @@ +CREATE TABLE saml2_asserting_party_metadata +( + entity_id VARCHAR(1000) NOT NULL, + singlesignon_url VARCHAR(1000) NOT NULL, + singlesignon_binding VARCHAR(100), + singlesignon_sign_request boolean, + signing_algorithms blob, + verification_credentials blob NOT NULL, + encryption_credentials blob, + singlelogout_url VARCHAR(1000), + singlelogout_response_url VARCHAR(1000), + singlelogout_binding VARCHAR(100), + PRIMARY KEY (entity_id) +); diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java new file mode 100644 index 0000000000..c734bcb236 --- /dev/null +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java @@ -0,0 +1,177 @@ +package org.springframework.security.saml2.provider.service.registration; + +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.serializer.DefaultSerializer; +import org.springframework.core.serializer.Serializer; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.security.saml2.core.Saml2X509Credential; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link JdbcAssertingPartyMetadataRepository} + */ +class JdbcAssertingPartyMetadataRepositoryTests { + + private static final String SCHEMA_SQL_RESOURCE = "org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql"; + + private static final String SAVE_SQL = "INSERT INTO saml2_asserting_party_metadata (" + + JdbcAssertingPartyMetadataRepository.COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + private static final String ENTITY_ID = "https://localhost/simplesaml/saml2/idp/metadata.php"; + + private static final String SINGLE_SIGNON_URL = "https://localhost/SSO"; + + private static final String SINGLE_SIGNON_BINDING = Saml2MessageBinding.REDIRECT.getUrn(); + + private static final boolean SINGLE_SIGNON_SIGN_REQUEST = false; + + private static final String SINGLE_LOGOUT_URL = "https://localhost/SLO"; + + private static final String SINGLE_LOGOUT_RESPONSE_URL = "https://localhost/SLO/response"; + + private static final String SINGLE_LOGOUT_BINDING = Saml2MessageBinding.REDIRECT.getUrn(); + + private static final List SIGNING_ALGORITHMS = List.of("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"); + + private X509Certificate certificate; + + private EmbeddedDatabase db; + + private JdbcAssertingPartyMetadataRepository repository; + + private JdbcOperations jdbcOperations; + + private final Serializer serializer = new DefaultSerializer(); + + @BeforeEach + public void setUp() throws Exception { + this.db = createDb(); + this.jdbcOperations = new JdbcTemplate(this.db); + this.repository = new JdbcAssertingPartyMetadataRepository(this.jdbcOperations); + this.certificate = loadCertificate("rsa.crt"); + } + + @AfterEach + public void tearDown() { + this.db.shutdown(); + } + + @Test + void constructorWhenJdbcOperationsIsNullThenThrowIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> new JdbcAssertingPartyMetadataRepository(null)) + .withMessage("jdbcOperations cannot be null"); + // @formatter:on + } + + @Test + void findByEntityIdWhenEntityIdIsNullThenThrowIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.repository.findByEntityId(null)) + .withMessage("entityId cannot be empty"); + // @formatter:on + } + + @Test + void findByEntityId() throws IOException { + this.jdbcOperations.update(SAVE_SQL, ENTITY_ID, SINGLE_SIGNON_URL, SINGLE_SIGNON_BINDING, + SINGLE_SIGNON_SIGN_REQUEST, this.serializer.serializeToByteArray(SIGNING_ALGORITHMS), + this.serializer.serializeToByteArray(asCredentials(this.certificate)), + this.serializer.serializeToByteArray(asCredentials(this.certificate)), SINGLE_LOGOUT_URL, + SINGLE_LOGOUT_RESPONSE_URL, SINGLE_LOGOUT_BINDING); + + AssertingPartyMetadata found = this.repository.findByEntityId(ENTITY_ID); + + assertThat(found).isNotNull(); + assertThat(found.getEntityId()).isEqualTo(ENTITY_ID); + assertThat(found.getSingleSignOnServiceLocation()).isEqualTo(SINGLE_SIGNON_URL); + assertThat(found.getSingleSignOnServiceBinding().getUrn()).isEqualTo(SINGLE_SIGNON_BINDING); + assertThat(found.getWantAuthnRequestsSigned()).isEqualTo(SINGLE_SIGNON_SIGN_REQUEST); + assertThat(found.getSingleLogoutServiceLocation()).isEqualTo(SINGLE_LOGOUT_URL); + assertThat(found.getSingleLogoutServiceResponseLocation()).isEqualTo(SINGLE_LOGOUT_RESPONSE_URL); + assertThat(found.getSingleLogoutServiceBinding().getUrn()).isEqualTo(SINGLE_LOGOUT_BINDING); + assertThat(found.getSigningAlgorithms()).contains(SIGNING_ALGORITHMS.get(0)); + assertThat(found.getVerificationX509Credentials()).hasSize(1); + assertThat(found.getEncryptionX509Credentials()).hasSize(1); + } + + @Test + void findByEntityIdWhenNotExists() { + AssertingPartyMetadata found = this.repository.findByEntityId("non-existent-entity-id"); + assertThat(found).isNull(); + } + + @Test + void iterator() throws IOException { + this.jdbcOperations.update(SAVE_SQL, ENTITY_ID, SINGLE_SIGNON_URL, SINGLE_SIGNON_BINDING, + SINGLE_SIGNON_SIGN_REQUEST, this.serializer.serializeToByteArray(SIGNING_ALGORITHMS), + this.serializer.serializeToByteArray(asCredentials(this.certificate)), + this.serializer.serializeToByteArray(asCredentials(this.certificate)), SINGLE_LOGOUT_URL, + SINGLE_LOGOUT_RESPONSE_URL, SINGLE_LOGOUT_BINDING); + + this.jdbcOperations.update(SAVE_SQL, "https://localhost/simplesaml2/saml2/idp/metadata.php", SINGLE_SIGNON_URL, + SINGLE_SIGNON_BINDING, SINGLE_SIGNON_SIGN_REQUEST, + this.serializer.serializeToByteArray(SIGNING_ALGORITHMS), + this.serializer.serializeToByteArray(asCredentials(this.certificate)), + this.serializer.serializeToByteArray(asCredentials(this.certificate)), SINGLE_LOGOUT_URL, + SINGLE_LOGOUT_RESPONSE_URL, SINGLE_LOGOUT_BINDING); + + Iterator iterator = this.repository.iterator(); + AssertingPartyMetadata first = iterator.next(); + assertThat(first).isNotNull(); + AssertingPartyMetadata second = iterator.next(); + assertThat(second).isNotNull(); + assertThat(iterator.hasNext()).isFalse(); + } + + private static EmbeddedDatabase createDb() { + return createDb(SCHEMA_SQL_RESOURCE); + } + + private static EmbeddedDatabase createDb(String schema) { + // @formatter:off + return new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .setType(EmbeddedDatabaseType.HSQL) + .setScriptEncoding("UTF-8") + .addScript(schema) + .build(); + // @formatter:on + } + + private X509Certificate loadCertificate(String path) { + try (InputStream is = new ClassPathResource(path).getInputStream()) { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) factory.generateCertificate(is); + } + catch (Exception ex) { + throw new RuntimeException("Error loading certificate from " + path, ex); + } + } + + private Collection asCredentials(X509Certificate certificate) { + return List.of(new Saml2X509Credential(certificate, Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION, + Saml2X509Credential.Saml2X509CredentialType.VERIFICATION)); + } + +} diff --git a/saml2/saml2-service-provider/src/test/resources/rsa.crt b/saml2/saml2-service-provider/src/test/resources/rsa.crt new file mode 100644 index 0000000000..aa147065de --- /dev/null +++ b/saml2/saml2-service-provider/src/test/resources/rsa.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID1zCCAr+gAwIBAgIUCzQeKBMTO0iHVW3iKmZC41haqCowDQYJKoZIhvcNAQEL +BQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI +Q2l0eU5hbWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55 +U2VjdGlvbk5hbWUxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzA5MjAwODI5MDNa +Fw0zMzA5MTcwODI5MDNaMHsxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5h +bWUxETAPBgNVBAcMCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkG +A1UECwwSQ29tcGFueVNlY3Rpb25OYW1lMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUfi4aaCotJZX6OSDjv6fxCCfc +ihSs91Z/mmN+yc1fsxVSs53SIbqUuo+Wzhv34kp8I/r03P9LWVTkFPbeDxAl75Oa +PGggxK55US0Zfy9Hj1BwWIKV3330N61emID1GDEtFKL4yJbJdreQXnIXTBL2o76V +nuV/tYozyZnb07IQ1WhUm5WDxgzM0yFudMynTczCBeZHfvharDtB8PFFhCZXW2/9 +TZVVfW4oOML8EAX3hvnvYBlFl/foxXekZSwq/odOkmWCZavT2+0sburHUlOnPGUh +Qj4tHwpMRczp7VX4ptV1D2UrxsK/2B+s9FK2QSLKQ9JzAYJ6WxQjHcvET9jvAgMB +AAGjUzBRMB0GA1UdDgQWBBQjDr/1E/01pfLPD8uWF7gbaYL0TTAfBgNVHSMEGDAW +gBQjDr/1E/01pfLPD8uWF7gbaYL0TTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQAGjUuec0+0XNMCRDKZslbImdCAVsKsEWk6NpnUViDFAxL+KQuC +NW131UeHb9SCzMqRwrY4QI3nAwJQCmilL/hFM3ss4acn3WHu1yci/iKPUKeL1ec5 +kCFUmqX1NpTiVaytZ/9TKEr69SMVqNfQiuW5U1bIIYTqK8xo46WpM6YNNHO3eJK6 +NH0MW79Wx5ryi4i4C6afqYbVbx7tqcmy8CFeNxgZ0bFQ87SiwYXIj77b6sVYbu32 +doykBQgSHLcagWASPQ73m73CWUgo+7+EqSKIQqORbgmTLPmOUh99gFIx7jmjTyHm +NBszx1ZVWuIv3mWmp626Kncyc+LLM9tvgymx +-----END CERTIFICATE----- From e2e42a5580bee84203ad9e1448269be91620dde2 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:45:51 -0600 Subject: [PATCH 104/149] Fix Checkstyle Issue gh-16012 --- ...AssertingPartyMetadataRepositoryTests.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java index c734bcb236..e8a76ee335 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.security.saml2.provider.service.registration; import java.io.IOException; @@ -62,7 +78,7 @@ class JdbcAssertingPartyMetadataRepositoryTests { private final Serializer serializer = new DefaultSerializer(); @BeforeEach - public void setUp() throws Exception { + void setUp() { this.db = createDb(); this.jdbcOperations = new JdbcTemplate(this.db); this.repository = new JdbcAssertingPartyMetadataRepository(this.jdbcOperations); @@ -70,7 +86,7 @@ class JdbcAssertingPartyMetadataRepositoryTests { } @AfterEach - public void tearDown() { + void tearDown() { this.db.shutdown(); } From 2bd05128ec614243f563a70fe0b6c09e726a29be Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:44:19 -0600 Subject: [PATCH 105/149] Add JdbcAssertingPartyMetadataRepository#save Issue gh-16012 Co-Authored-By: chao.wang --- .../JdbcAssertingPartyMetadataRepository.java | 78 ++++++++++++ ...AssertingPartyMetadataRepositoryTests.java | 120 ++++++------------ 2 files changed, 117 insertions(+), 81 deletions(-) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java index 620e6bdf2e..88f4a59a71 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java @@ -19,16 +19,20 @@ package org.springframework.security.saml2.provider.service.registration; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.function.Function; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; import org.springframework.core.serializer.DefaultDeserializer; +import org.springframework.core.serializer.DefaultSerializer; import org.springframework.core.serializer.Deserializer; +import org.springframework.core.serializer.Serializer; import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.PreparedStatementSetter; @@ -37,6 +41,7 @@ import org.springframework.jdbc.core.SqlParameterValue; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails; import org.springframework.util.Assert; +import org.springframework.util.function.ThrowingFunction; /** * A JDBC implementation of {@link AssertingPartyMetadataRepository}. @@ -51,6 +56,8 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart private RowMapper assertingPartyMetadataRowMapper = new AssertingPartyMetadataRowMapper( ResultSet::getBytes); + private final AssertingPartyMetadataParametersMapper assertingPartyMetadataParametersMapper = new AssertingPartyMetadataParametersMapper(); + // @formatter:off static final String COLUMN_NAMES = "entity_id, " + "singlesignon_url, " @@ -77,6 +84,25 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart + " FROM " + TABLE_NAME; // @formatter:on + // @formatter:off + private static final String SAVE_CREDENTIAL_RECORD_SQL = "INSERT INTO " + TABLE_NAME + + " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + // @formatter:on + + // @formatter:off + private static final String UPDATE_CREDENTIAL_RECORD_SQL = "UPDATE " + TABLE_NAME + + " SET singlesignon_url = ?, " + + "singlesignon_binding = ?, " + + "singlesignon_sign_request = ?, " + + "signing_algorithms = ?, " + + "verification_credentials = ?, " + + "encryption_credentials = ?, " + + "singlelogout_url = ?, " + + "singlelogout_response_url = ?, " + + "singlelogout_binding = ?" + + " WHERE " + ENTITY_ID_FILTER; + // @formatter:on + /** * Constructs a {@code JdbcRelyingPartyRegistrationRepository} using the provided * parameters. @@ -116,6 +142,30 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart return result.iterator(); } + /** + * Persist this {@link AssertingPartyMetadata} + * @param metadata the metadata to persist + */ + public void save(AssertingPartyMetadata metadata) { + Assert.notNull(metadata, "metadata cannot be null"); + int rows = updateCredentialRecord(metadata); + if (rows == 0) { + insertCredentialRecord(metadata); + } + } + + private void insertCredentialRecord(AssertingPartyMetadata metadata) { + List parameters = this.assertingPartyMetadataParametersMapper.apply(metadata); + this.jdbcOperations.update(SAVE_CREDENTIAL_RECORD_SQL, parameters.toArray()); + } + + private int updateCredentialRecord(AssertingPartyMetadata metadata) { + List parameters = this.assertingPartyMetadataParametersMapper.apply(metadata); + SqlParameterValue credentialId = parameters.remove(0); + parameters.add(credentialId); + return this.jdbcOperations.update(UPDATE_CREDENTIAL_RECORD_SQL, parameters.toArray()); + } + /** * The default {@link RowMapper} that maps the current row in * {@code java.sql.ResultSet} to {@link AssertingPartyMetadata}. @@ -181,6 +231,34 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart } + private static class AssertingPartyMetadataParametersMapper + implements Function> { + + private final Serializer serializer = new DefaultSerializer(); + + @Override + public List apply(AssertingPartyMetadata record) { + List parameters = new ArrayList<>(); + + parameters.add(new SqlParameterValue(Types.VARCHAR, record.getEntityId())); + parameters.add(new SqlParameterValue(Types.VARCHAR, record.getSingleSignOnServiceLocation())); + parameters.add(new SqlParameterValue(Types.VARCHAR, record.getSingleSignOnServiceBinding().getUrn())); + parameters.add(new SqlParameterValue(Types.BOOLEAN, record.getWantAuthnRequestsSigned())); + ThrowingFunction, byte[]> algorithms = this.serializer::serializeToByteArray; + parameters.add(new SqlParameterValue(Types.BLOB, algorithms.apply(record.getSigningAlgorithms()))); + ThrowingFunction, byte[]> credentials = this.serializer::serializeToByteArray; + parameters + .add(new SqlParameterValue(Types.BLOB, credentials.apply(record.getVerificationX509Credentials()))); + parameters.add(new SqlParameterValue(Types.BLOB, credentials.apply(record.getEncryptionX509Credentials()))); + parameters.add(new SqlParameterValue(Types.VARCHAR, record.getSingleLogoutServiceLocation())); + parameters.add(new SqlParameterValue(Types.VARCHAR, record.getSingleLogoutServiceResponseLocation())); + parameters.add(new SqlParameterValue(Types.VARCHAR, record.getSingleLogoutServiceBinding().getUrn())); + + return parameters; + } + + } + private interface GetBytes { byte[] getBytes(ResultSet rs, String columnName) throws SQLException; diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java index e8a76ee335..785e4a12f3 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java @@ -16,27 +16,17 @@ package org.springframework.security.saml2.provider.service.registration; -import java.io.IOException; -import java.io.InputStream; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Collection; import java.util.Iterator; -import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.serializer.DefaultSerializer; -import org.springframework.core.serializer.Serializer; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.saml2.core.Saml2X509Credential; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -48,41 +38,21 @@ class JdbcAssertingPartyMetadataRepositoryTests { private static final String SCHEMA_SQL_RESOURCE = "org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql"; - private static final String SAVE_SQL = "INSERT INTO saml2_asserting_party_metadata (" - + JdbcAssertingPartyMetadataRepository.COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - - private static final String ENTITY_ID = "https://localhost/simplesaml/saml2/idp/metadata.php"; - - private static final String SINGLE_SIGNON_URL = "https://localhost/SSO"; - - private static final String SINGLE_SIGNON_BINDING = Saml2MessageBinding.REDIRECT.getUrn(); - - private static final boolean SINGLE_SIGNON_SIGN_REQUEST = false; - - private static final String SINGLE_LOGOUT_URL = "https://localhost/SLO"; - - private static final String SINGLE_LOGOUT_RESPONSE_URL = "https://localhost/SLO/response"; - - private static final String SINGLE_LOGOUT_BINDING = Saml2MessageBinding.REDIRECT.getUrn(); - - private static final List SIGNING_ALGORITHMS = List.of("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"); - - private X509Certificate certificate; - private EmbeddedDatabase db; private JdbcAssertingPartyMetadataRepository repository; private JdbcOperations jdbcOperations; - private final Serializer serializer = new DefaultSerializer(); + private final AssertingPartyMetadata metadata = TestRelyingPartyRegistrations.full() + .build() + .getAssertingPartyMetadata(); @BeforeEach void setUp() { this.db = createDb(); this.jdbcOperations = new JdbcTemplate(this.db); this.repository = new JdbcAssertingPartyMetadataRepository(this.jdbcOperations); - this.certificate = loadCertificate("rsa.crt"); } @AfterEach @@ -109,26 +79,12 @@ class JdbcAssertingPartyMetadataRepositoryTests { } @Test - void findByEntityId() throws IOException { - this.jdbcOperations.update(SAVE_SQL, ENTITY_ID, SINGLE_SIGNON_URL, SINGLE_SIGNON_BINDING, - SINGLE_SIGNON_SIGN_REQUEST, this.serializer.serializeToByteArray(SIGNING_ALGORITHMS), - this.serializer.serializeToByteArray(asCredentials(this.certificate)), - this.serializer.serializeToByteArray(asCredentials(this.certificate)), SINGLE_LOGOUT_URL, - SINGLE_LOGOUT_RESPONSE_URL, SINGLE_LOGOUT_BINDING); + void findByEntityId() { + this.repository.save(this.metadata); - AssertingPartyMetadata found = this.repository.findByEntityId(ENTITY_ID); + AssertingPartyMetadata found = this.repository.findByEntityId(this.metadata.getEntityId()); - assertThat(found).isNotNull(); - assertThat(found.getEntityId()).isEqualTo(ENTITY_ID); - assertThat(found.getSingleSignOnServiceLocation()).isEqualTo(SINGLE_SIGNON_URL); - assertThat(found.getSingleSignOnServiceBinding().getUrn()).isEqualTo(SINGLE_SIGNON_BINDING); - assertThat(found.getWantAuthnRequestsSigned()).isEqualTo(SINGLE_SIGNON_SIGN_REQUEST); - assertThat(found.getSingleLogoutServiceLocation()).isEqualTo(SINGLE_LOGOUT_URL); - assertThat(found.getSingleLogoutServiceResponseLocation()).isEqualTo(SINGLE_LOGOUT_RESPONSE_URL); - assertThat(found.getSingleLogoutServiceBinding().getUrn()).isEqualTo(SINGLE_LOGOUT_BINDING); - assertThat(found.getSigningAlgorithms()).contains(SIGNING_ALGORITHMS.get(0)); - assertThat(found.getVerificationX509Credentials()).hasSize(1); - assertThat(found.getEncryptionX509Credentials()).hasSize(1); + assertAssertingPartyEquals(found, this.metadata); } @Test @@ -138,28 +94,30 @@ class JdbcAssertingPartyMetadataRepositoryTests { } @Test - void iterator() throws IOException { - this.jdbcOperations.update(SAVE_SQL, ENTITY_ID, SINGLE_SIGNON_URL, SINGLE_SIGNON_BINDING, - SINGLE_SIGNON_SIGN_REQUEST, this.serializer.serializeToByteArray(SIGNING_ALGORITHMS), - this.serializer.serializeToByteArray(asCredentials(this.certificate)), - this.serializer.serializeToByteArray(asCredentials(this.certificate)), SINGLE_LOGOUT_URL, - SINGLE_LOGOUT_RESPONSE_URL, SINGLE_LOGOUT_BINDING); - - this.jdbcOperations.update(SAVE_SQL, "https://localhost/simplesaml2/saml2/idp/metadata.php", SINGLE_SIGNON_URL, - SINGLE_SIGNON_BINDING, SINGLE_SIGNON_SIGN_REQUEST, - this.serializer.serializeToByteArray(SIGNING_ALGORITHMS), - this.serializer.serializeToByteArray(asCredentials(this.certificate)), - this.serializer.serializeToByteArray(asCredentials(this.certificate)), SINGLE_LOGOUT_URL, - SINGLE_LOGOUT_RESPONSE_URL, SINGLE_LOGOUT_BINDING); + void iterator() { + AssertingPartyMetadata second = RelyingPartyRegistration.withAssertingPartyMetadata(this.metadata) + .assertingPartyMetadata((a) -> a.entityId("https://example.org/idp")) + .build() + .getAssertingPartyMetadata(); + this.repository.save(this.metadata); + this.repository.save(second); Iterator iterator = this.repository.iterator(); - AssertingPartyMetadata first = iterator.next(); - assertThat(first).isNotNull(); - AssertingPartyMetadata second = iterator.next(); - assertThat(second).isNotNull(); + + assertAssertingPartyEquals(iterator.next(), this.metadata); + assertAssertingPartyEquals(iterator.next(), second); assertThat(iterator.hasNext()).isFalse(); } + @Test + void saveWhenExistingThenUpdates() { + this.repository.save(this.metadata); + boolean existing = this.metadata.getWantAuthnRequestsSigned(); + this.repository.save(this.metadata.mutate().wantAuthnRequestsSigned(!existing).build()); + boolean updated = this.repository.findByEntityId(this.metadata.getEntityId()).getWantAuthnRequestsSigned(); + assertThat(existing).isNotEqualTo(updated); + } + private static EmbeddedDatabase createDb() { return createDb(SCHEMA_SQL_RESOURCE); } @@ -175,19 +133,19 @@ class JdbcAssertingPartyMetadataRepositoryTests { // @formatter:on } - private X509Certificate loadCertificate(String path) { - try (InputStream is = new ClassPathResource(path).getInputStream()) { - CertificateFactory factory = CertificateFactory.getInstance("X.509"); - return (X509Certificate) factory.generateCertificate(is); - } - catch (Exception ex) { - throw new RuntimeException("Error loading certificate from " + path, ex); - } - } - - private Collection asCredentials(X509Certificate certificate) { - return List.of(new Saml2X509Credential(certificate, Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION, - Saml2X509Credential.Saml2X509CredentialType.VERIFICATION)); + private void assertAssertingPartyEquals(AssertingPartyMetadata found, AssertingPartyMetadata expected) { + assertThat(found).isNotNull(); + assertThat(found.getEntityId()).isEqualTo(expected.getEntityId()); + assertThat(found.getSingleSignOnServiceLocation()).isEqualTo(expected.getSingleSignOnServiceLocation()); + assertThat(found.getSingleSignOnServiceBinding()).isEqualTo(expected.getSingleSignOnServiceBinding()); + assertThat(found.getWantAuthnRequestsSigned()).isEqualTo(expected.getWantAuthnRequestsSigned()); + assertThat(found.getSingleLogoutServiceLocation()).isEqualTo(expected.getSingleLogoutServiceLocation()); + assertThat(found.getSingleLogoutServiceResponseLocation()) + .isEqualTo(expected.getSingleLogoutServiceResponseLocation()); + assertThat(found.getSingleLogoutServiceBinding()).isEqualTo(expected.getSingleLogoutServiceBinding()); + assertThat(found.getSigningAlgorithms()).containsAll(expected.getSigningAlgorithms()); + assertThat(found.getVerificationX509Credentials()).containsAll(expected.getVerificationX509Credentials()); + assertThat(found.getEncryptionX509Credentials()).containsAll(expected.getEncryptionX509Credentials()); } } From e8f920e0eeafff1af55427c13db054a3b6b13830 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:01:51 -0600 Subject: [PATCH 106/149] Polish JdbcAssertingPartyMetadataRepository - Remove GetBytes since it's not used yet - Remove customizable RowMapper since this can be added later - Change signing_algorithms to be a String since the conversion strategy is simple - Standardize test names - Simplify conversion of credentials using ThrowingFunction - Change column names to match RelyingPartyRegistration field names Issue gh-16012 --- .../JdbcAssertingPartyMetadataRepository.java | 129 ++++++------------ ...serting-party-metadata-schema-postgres.sql | 20 +-- .../saml2-asserting-party-metadata-schema.sql | 20 +-- ...AssertingPartyMetadataRepositoryTests.java | 11 +- 4 files changed, 63 insertions(+), 117 deletions(-) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java index 88f4a59a71..9cf3234ea9 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepository.java @@ -20,15 +20,13 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.function.Function; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; import org.springframework.core.serializer.DefaultDeserializer; import org.springframework.core.serializer.DefaultSerializer; import org.springframework.core.serializer.Deserializer; @@ -53,22 +51,22 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart private final JdbcOperations jdbcOperations; - private RowMapper assertingPartyMetadataRowMapper = new AssertingPartyMetadataRowMapper( - ResultSet::getBytes); + private final RowMapper assertingPartyMetadataRowMapper = new AssertingPartyMetadataRowMapper(); private final AssertingPartyMetadataParametersMapper assertingPartyMetadataParametersMapper = new AssertingPartyMetadataParametersMapper(); // @formatter:off - static final String COLUMN_NAMES = "entity_id, " - + "singlesignon_url, " - + "singlesignon_binding, " - + "singlesignon_sign_request, " - + "signing_algorithms, " - + "verification_credentials, " - + "encryption_credentials, " - + "singlelogout_url, " - + "singlelogout_response_url, " - + "singlelogout_binding"; + static final String[] COLUMN_NAMES = { "entity_id", + "single_sign_on_service_location", + "single_sign_on_service_binding", + "want_authn_requests_signed", + "signing_algorithms", + "verification_credentials", + "encryption_credentials", + "single_logout_service_location", + "single_logout_service_response_location", + "single_logout_service_binding" }; + // @formatter:on private static final String TABLE_NAME = "saml2_asserting_party_metadata"; @@ -76,30 +74,23 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart private static final String ENTITY_ID_FILTER = "entity_id = ?"; // @formatter:off - private static final String LOAD_BY_ID_SQL = "SELECT " + COLUMN_NAMES + private static final String LOAD_BY_ID_SQL = "SELECT " + String.join(",", COLUMN_NAMES) + " FROM " + TABLE_NAME + " WHERE " + ENTITY_ID_FILTER; - private static final String LOAD_ALL_SQL = "SELECT " + COLUMN_NAMES + private static final String LOAD_ALL_SQL = "SELECT " + String.join(",", COLUMN_NAMES) + " FROM " + TABLE_NAME; // @formatter:on // @formatter:off private static final String SAVE_CREDENTIAL_RECORD_SQL = "INSERT INTO " + TABLE_NAME - + " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + " (" + String.join(",", COLUMN_NAMES) + ") VALUES (" + String.join(",", Collections.nCopies(COLUMN_NAMES.length, "?")) + ")"; // @formatter:on // @formatter:off private static final String UPDATE_CREDENTIAL_RECORD_SQL = "UPDATE " + TABLE_NAME - + " SET singlesignon_url = ?, " - + "singlesignon_binding = ?, " - + "singlesignon_sign_request = ?, " - + "signing_algorithms = ?, " - + "verification_credentials = ?, " - + "encryption_credentials = ?, " - + "singlelogout_url = ?, " - + "singlelogout_response_url = ?, " - + "singlelogout_binding = ?" + + " SET " + String.join(" = ?,", Arrays.copyOfRange(COLUMN_NAMES, 1, COLUMN_NAMES.length)) + + " = ?" + " WHERE " + ENTITY_ID_FILTER; // @formatter:on @@ -113,18 +104,6 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart this.jdbcOperations = jdbcOperations; } - /** - * Sets the {@link RowMapper} used for mapping the current row in - * {@code java.sql.ResultSet} to {@link AssertingPartyMetadata}. The default is - * {@link AssertingPartyMetadataRowMapper}. - * @param assertingPartyMetadataRowMapper the {@link RowMapper} used for mapping the - * current row in {@code java.sql.ResultSet} to {@link AssertingPartyMetadata} - */ - public void setAssertingPartyMetadataRowMapper(RowMapper assertingPartyMetadataRowMapper) { - Assert.notNull(assertingPartyMetadataRowMapper, "assertingPartyMetadataRowMapper cannot be null"); - this.assertingPartyMetadataRowMapper = assertingPartyMetadataRowMapper; - } - @Override public AssertingPartyMetadata findByEntityId(String entityId) { Assert.hasText(entityId, "entityId cannot be empty"); @@ -172,52 +151,26 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart */ private static final class AssertingPartyMetadataRowMapper implements RowMapper { - private final Log logger = LogFactory.getLog(AssertingPartyMetadataRowMapper.class); - private final Deserializer deserializer = new DefaultDeserializer(); - private final GetBytes getBytes; - - AssertingPartyMetadataRowMapper(GetBytes getBytes) { - this.getBytes = getBytes; - } - @Override public AssertingPartyMetadata mapRow(ResultSet rs, int rowNum) throws SQLException { - String entityId = rs.getString("entity_id"); - String singleSignOnUrl = rs.getString("singlesignon_url"); - Saml2MessageBinding singleSignOnBinding = Saml2MessageBinding.from(rs.getString("singlesignon_binding")); - boolean singleSignOnSignRequest = rs.getBoolean("singlesignon_sign_request"); - String singleLogoutUrl = rs.getString("singlelogout_url"); - String singleLogoutResponseUrl = rs.getString("singlelogout_response_url"); - Saml2MessageBinding singleLogoutBinding = Saml2MessageBinding.from(rs.getString("singlelogout_binding")); - byte[] signingAlgorithmsBytes = this.getBytes.getBytes(rs, "signing_algorithms"); - byte[] verificationCredentialsBytes = this.getBytes.getBytes(rs, "verification_credentials"); - byte[] encryptionCredentialsBytes = this.getBytes.getBytes(rs, "encryption_credentials"); - + String entityId = rs.getString(COLUMN_NAMES[0]); + String singleSignOnUrl = rs.getString(COLUMN_NAMES[1]); + Saml2MessageBinding singleSignOnBinding = Saml2MessageBinding.from(rs.getString(COLUMN_NAMES[2])); + boolean singleSignOnSignRequest = rs.getBoolean(COLUMN_NAMES[3]); + List algorithms = List.of(rs.getString(COLUMN_NAMES[4]).split(",")); + byte[] verificationCredentialsBytes = rs.getBytes(COLUMN_NAMES[5]); + byte[] encryptionCredentialsBytes = rs.getBytes(COLUMN_NAMES[6]); + ThrowingFunction> credentials = ( + bytes) -> (Collection) this.deserializer.deserializeFromByteArray(bytes); AssertingPartyMetadata.Builder builder = new AssertingPartyDetails.Builder(); - try { - if (signingAlgorithmsBytes != null) { - List signingAlgorithms = (List) this.deserializer - .deserializeFromByteArray(signingAlgorithmsBytes); - builder.signingAlgorithms((algorithms) -> algorithms.addAll(signingAlgorithms)); - } - if (verificationCredentialsBytes != null) { - Collection verificationCredentials = (Collection) this.deserializer - .deserializeFromByteArray(verificationCredentialsBytes); - builder.verificationX509Credentials((credentials) -> credentials.addAll(verificationCredentials)); - } - if (encryptionCredentialsBytes != null) { - Collection encryptionCredentials = (Collection) this.deserializer - .deserializeFromByteArray(encryptionCredentialsBytes); - builder.encryptionX509Credentials((credentials) -> credentials.addAll(encryptionCredentials)); - } - } - catch (Exception ex) { - this.logger.debug(LogMessage.format("Parsing serialized credentials for entity %s failed", entityId), - ex); - return null; - } + Collection verificationCredentials = credentials.apply(verificationCredentialsBytes); + Collection encryptionCredentials = (encryptionCredentialsBytes != null) + ? credentials.apply(encryptionCredentialsBytes) : List.of(); + String singleLogoutUrl = rs.getString(COLUMN_NAMES[7]); + String singleLogoutResponseUrl = rs.getString(COLUMN_NAMES[8]); + Saml2MessageBinding singleLogoutBinding = Saml2MessageBinding.from(rs.getString(COLUMN_NAMES[9])); builder.entityId(entityId) .wantAuthnRequestsSigned(singleSignOnSignRequest) @@ -225,7 +178,10 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart .singleSignOnServiceBinding(singleSignOnBinding) .singleLogoutServiceLocation(singleLogoutUrl) .singleLogoutServiceBinding(singleLogoutBinding) - .singleLogoutServiceResponseLocation(singleLogoutResponseUrl); + .singleLogoutServiceResponseLocation(singleLogoutResponseUrl) + .signingAlgorithms((a) -> a.addAll(algorithms)) + .verificationX509Credentials((c) -> c.addAll(verificationCredentials)) + .encryptionX509Credentials((c) -> c.addAll(encryptionCredentials)); return builder.build(); } @@ -244,8 +200,7 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart parameters.add(new SqlParameterValue(Types.VARCHAR, record.getSingleSignOnServiceLocation())); parameters.add(new SqlParameterValue(Types.VARCHAR, record.getSingleSignOnServiceBinding().getUrn())); parameters.add(new SqlParameterValue(Types.BOOLEAN, record.getWantAuthnRequestsSigned())); - ThrowingFunction, byte[]> algorithms = this.serializer::serializeToByteArray; - parameters.add(new SqlParameterValue(Types.BLOB, algorithms.apply(record.getSigningAlgorithms()))); + parameters.add(new SqlParameterValue(Types.BLOB, String.join(",", record.getSigningAlgorithms()))); ThrowingFunction, byte[]> credentials = this.serializer::serializeToByteArray; parameters .add(new SqlParameterValue(Types.BLOB, credentials.apply(record.getVerificationX509Credentials()))); @@ -259,10 +214,4 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart } - private interface GetBytes { - - byte[] getBytes(ResultSet rs, String columnName) throws SQLException; - - } - } diff --git a/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema-postgres.sql b/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema-postgres.sql index ffa047fe7b..130e141f59 100644 --- a/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema-postgres.sql +++ b/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema-postgres.sql @@ -1,14 +1,14 @@ CREATE TABLE saml2_asserting_party_metadata ( - entity_id VARCHAR(1000) NOT NULL, - singlesignon_url VARCHAR(1000) NOT NULL, - singlesignon_binding VARCHAR(100), - singlesignon_sign_request boolean, - signing_algorithms BYTEA, - verification_credentials BYTEA NOT NULL, - encryption_credentials BYTEA, - singlelogout_url VARCHAR(1000), - singlelogout_response_url VARCHAR(1000), - singlelogout_binding VARCHAR(100), + entity_id VARCHAR(1000) NOT NULL, + single_sign_on_service_location VARCHAR(1000) NOT NULL, + single_sign_on_service_binding VARCHAR(100), + want_authn_requests_signed boolean, + signing_algorithms BYTEA, + verification_credentials BYTEA NOT NULL, + encryption_credentials BYTEA, + single_logout_service_location VARCHAR(1000), + single_logout_service_response_location VARCHAR(1000), + single_logout_service_binding VARCHAR(100), PRIMARY KEY (entity_id) ); diff --git a/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql b/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql index 2fd6cb8cdf..5c9b6a4826 100644 --- a/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql +++ b/saml2/saml2-service-provider/src/main/resources/org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql @@ -1,14 +1,14 @@ CREATE TABLE saml2_asserting_party_metadata ( - entity_id VARCHAR(1000) NOT NULL, - singlesignon_url VARCHAR(1000) NOT NULL, - singlesignon_binding VARCHAR(100), - singlesignon_sign_request boolean, - signing_algorithms blob, - verification_credentials blob NOT NULL, - encryption_credentials blob, - singlelogout_url VARCHAR(1000), - singlelogout_response_url VARCHAR(1000), - singlelogout_binding VARCHAR(100), + entity_id VARCHAR(1000) NOT NULL, + single_sign_on_service_location VARCHAR(1000) NOT NULL, + single_sign_on_service_binding VARCHAR(100), + want_authn_requests_signed boolean, + signing_algorithms VARCHAR(256) NOT NULL, + verification_credentials blob NOT NULL, + encryption_credentials blob, + single_logout_service_location VARCHAR(1000), + single_logout_service_response_location VARCHAR(1000), + single_logout_service_binding VARCHAR(100), PRIMARY KEY (entity_id) ); diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java index 785e4a12f3..56f871a664 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/JdbcAssertingPartyMetadataRepositoryTests.java @@ -79,7 +79,7 @@ class JdbcAssertingPartyMetadataRepositoryTests { } @Test - void findByEntityId() { + void findByEntityIdWhenEntityPresentThenReturns() { this.repository.save(this.metadata); AssertingPartyMetadata found = this.repository.findByEntityId(this.metadata.getEntityId()); @@ -88,17 +88,14 @@ class JdbcAssertingPartyMetadataRepositoryTests { } @Test - void findByEntityIdWhenNotExists() { + void findByEntityIdWhenNotExistsThenNull() { AssertingPartyMetadata found = this.repository.findByEntityId("non-existent-entity-id"); assertThat(found).isNull(); } @Test - void iterator() { - AssertingPartyMetadata second = RelyingPartyRegistration.withAssertingPartyMetadata(this.metadata) - .assertingPartyMetadata((a) -> a.entityId("https://example.org/idp")) - .build() - .getAssertingPartyMetadata(); + void iteratorWhenEnitiesExistThenContains() { + AssertingPartyMetadata second = this.metadata.mutate().entityId("https://example.org/idp").build(); this.repository.save(this.metadata); this.repository.save(second); From aba437d4698c0b30559021e3f41b85607fee8ce0 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Fri, 16 May 2025 14:07:10 +0300 Subject: [PATCH 107/149] Add Support SubjectX500PrincipalExtractor Closes gh-16980 Signed-off-by: Max Batischev --- .../web/configurers/X509Configurer.java | 28 +++++- .../http/AuthenticationConfigBuilder.java | 16 +++- .../config/web/server/ServerHttpSecurity.java | 12 +-- .../security/config/spring-security-7.0.rnc | 3 + .../security/config/spring-security-7.0.xsd | 6 ++ .../web/configurers/X509ConfigurerTests.java | 39 +++++++- config/src/test/resources/max.cer | 17 ++++ .../servlet/appendix/namespace/http.adoc | 3 + .../x509/SubjectDnX509PrincipalExtractor.java | 6 +- .../x509/SubjectX500PrincipalExtractor.java | 88 +++++++++++++++++++ .../x509/X509AuthenticationFilter.java | 4 +- .../SubjectX500PrincipalExtractorTests.java | 66 ++++++++++++++ 12 files changed, 276 insertions(+), 12 deletions(-) create mode 100644 config/src/test/resources/max.cer create mode 100644 web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java create mode 100644 web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java index a3818e2a9a..b9b766a14d 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails; import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; @@ -74,6 +75,7 @@ import org.springframework.security.web.context.RequestAttributeSecurityContextR * * @author Rob Winch * @author Ngoc Nhan + * @author Max Batischev * @since 3.2 */ public final class X509Configurer> @@ -161,14 +163,38 @@ public final class X509Configurer> * @param subjectPrincipalRegex the regex to extract the user principal from the * certificate (i.e. "CN=(.*?)(?:,|$)"). * @return the {@link X509Configurer} for further customizations + * @deprecated Please use {{@link #extractPrincipalNameFromEmail(boolean)}} instead */ + @Deprecated public X509Configurer subjectPrincipalRegex(String subjectPrincipalRegex) { + if (this.x509PrincipalExtractor instanceof SubjectX500PrincipalExtractor) { + throw new IllegalStateException( + "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " + + "Please use one or the other."); + } SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); principalExtractor.setSubjectDnRegex(subjectPrincipalRegex); this.x509PrincipalExtractor = principalExtractor; return this; } + /** + * If true then DN will be extracted from EMAIlADDRESS, defaults to {@code false} + * @param extractPrincipalNameFromEmail whether to extract DN from EMAIlADDRESS + * @since 7.0 + */ + public X509Configurer extractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) { + if (this.x509PrincipalExtractor instanceof SubjectDnX509PrincipalExtractor) { + throw new IllegalStateException( + "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " + + "Please use one or the other."); + } + SubjectX500PrincipalExtractor extractor = new SubjectX500PrincipalExtractor(); + extractor.setExtractPrincipalNameFromEmail(extractPrincipalNameFromEmail); + this.x509PrincipalExtractor = extractor; + return this; + } + @Override public void init(H http) { PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider(); diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java index f997e5fa5e..9b6ee210ac 100644 --- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,7 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedG import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource; import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter; import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter; @@ -522,12 +523,25 @@ final class AuthenticationConfigBuilder { filterBuilder.addPropertyValue("securityContextHolderStrategy", authenticationFilterSecurityContextHolderStrategyRef); String regex = x509Elt.getAttribute("subject-principal-regex"); + String extractPrincipalNameFromEmail = x509Elt.getAttribute("extract-principal-name-from-email"); + if (StringUtils.hasText(regex) && StringUtils.hasText(extractPrincipalNameFromEmail)) { + throw new IllegalStateException( + "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " + + "Please use one or the other."); + } if (StringUtils.hasText(regex)) { BeanDefinitionBuilder extractor = BeanDefinitionBuilder .rootBeanDefinition(SubjectDnX509PrincipalExtractor.class); extractor.addPropertyValue("subjectDnRegex", regex); filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition()); } + if (StringUtils.hasText(extractPrincipalNameFromEmail)) { + BeanDefinitionBuilder extractor = BeanDefinitionBuilder + .rootBeanDefinition(SubjectX500PrincipalExtractor.class); + extractor.addPropertyValue("extractPrincipalNameFromEmail", + Boolean.parseBoolean(extractPrincipalNameFromEmail)); + filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition()); + } injectAuthenticationDetailsSource(x509Elt, filterBuilder); filter = (RootBeanDefinition) filterBuilder.getBeanDefinition(); createPrauthEntryPoint(x509Elt); 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 a4063fced4..f0760bc64e 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 @@ -119,7 +119,7 @@ import org.springframework.security.oauth2.server.resource.web.server.BearerToke import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter; import org.springframework.security.web.PortMapper; import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.server.DefaultServerRedirectStrategy; import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint; @@ -944,8 +944,8 @@ public class ServerHttpSecurity { * } * * - * Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} - * will be used. If authenticationManager is not specified, + * Note that if extractor is not specified, {@link SubjectX500PrincipalExtractor} will + * be used. If authenticationManager is not specified, * {@link ReactivePreAuthenticatedAuthenticationManager} will be used. * @return the {@link X509Spec} to customize * @since 5.2 @@ -979,8 +979,8 @@ public class ServerHttpSecurity { * } * * - * Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} - * will be used. If authenticationManager is not specified, + * Note that if extractor is not specified, {@link SubjectX500PrincipalExtractor} will + * be used. If authenticationManager is not specified, * {@link ReactivePreAuthenticatedAuthenticationManager} will be used. * @param x509Customizer the {@link Customizer} to provide more options for the * {@link X509Spec} @@ -4181,7 +4181,7 @@ public class ServerHttpSecurity { if (this.principalExtractor != null) { return this.principalExtractor; } - return new SubjectDnX509PrincipalExtractor(); + return new SubjectX500PrincipalExtractor(); } private ReactiveAuthenticationManager getAuthenticationManager() { diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc index bbf8622dfe..1236a2aec5 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc @@ -1053,6 +1053,9 @@ x509.attlist &= x509.attlist &= ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter attribute authentication-details-source-ref {xsd:token}? +x509.attlist &= + ## If true then DN will be extracted from EMAIlADDRESS + attribute extract-principal-name-from-email {xsd:token}? jee = ## Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication. diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd index 2e3d6cf275..ed82f3468b 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd @@ -2917,6 +2917,12 @@ + + + If true then DN will be extracted from EMAIlADDRESS + + + diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java index 206c0b7ece..38983691e1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -123,6 +123,16 @@ public class X509ConfigurerTests { // @formatter:on } + @Test + public void x509WhenExtractPrincipalNameFromEmailIsTrueThenUsesEmailAddressToExtractPrincipal() throws Exception { + this.spring.register(EmailPrincipalConfig.class).autowire(); + X509Certificate certificate = loadCert("max.cer"); + // @formatter:off + this.mvc.perform(get("/").with(x509(certificate))) + .andExpect(authenticated().withUsername("maxbatischev@gmail.com")); + // @formatter:on + } + @Test public void x509WhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception { this.spring.register(UserDetailsServiceBeanConfig.class).autowire(); @@ -277,6 +287,33 @@ public class X509ConfigurerTests { } + @Configuration + @EnableWebSecurity + static class EmailPrincipalConfig { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .x509((x509) -> + x509.extractPrincipalNameFromEmail(true) + ); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService userDetailsService() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("maxbatischev@gmail.com") + .password("password") + .roles("USER", "ADMIN") + .build(); + return new InMemoryUserDetailsManager(user); + } + + } + @Configuration @EnableWebSecurity static class UserDetailsServiceBeanConfig { diff --git a/config/src/test/resources/max.cer b/config/src/test/resources/max.cer new file mode 100644 index 0000000000..bd79b1f096 --- /dev/null +++ b/config/src/test/resources/max.cer @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICojCCAgugAwIBAgIBADANBgkqhkiG9w0BAQ0FADBuMQswCQYDVQQGEwJydTEP +MA0GA1UECAwGTW9zY293MQ8wDQYDVQQKDAZTcHJpbmcxFjAUBgNVBAMMDU1heCBC +YXRpc2NoZXYxJTAjBgkqhkiG9w0BCQEWFm1heGJhdGlzY2hldkBnbWFpbC5jb20w +HhcNMjUwNTE0MTcyODM5WhcNMjYwNTE0MTcyODM5WjBuMQswCQYDVQQGEwJydTEP +MA0GA1UECAwGTW9zY293MQ8wDQYDVQQKDAZTcHJpbmcxFjAUBgNVBAMMDU1heCBC +YXRpc2NoZXYxJTAjBgkqhkiG9w0BCQEWFm1heGJhdGlzY2hldkBnbWFpbC5jb20w +gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALVZ2K/iOINeHZ4XAV3QmNRgS+iB +Vw0fW07uzYkCoSZU1lOBQE0k8+fdM2+X9AsgwfRCE3tUZquPApEKynB5V9Seh+bR +vc9aj7PunMyN+zjRU6X7/BL3VqLfrJLSc15bQaSN1phJ6NT+BTXPTuiPbXldnJLC +wVo6PView83yZ335AgMBAAGjUDBOMB0GA1UdDgQWBBQhyQfxL2ZYotcS8AmMJtli +2IRAMTAfBgNVHSMEGDAWgBQhyQfxL2ZYotcS8AmMJtli2IRAMTAMBgNVHRMEBTAD +AQH/MA0GCSqGSIb3DQEBDQUAA4GBAIIIJxpsTPtUEnePAqqgVFWDKC2CExhtCBYL +MjLSC+7E9OlfuuX1joAsD4Yv86k4Ox836D0KQtINtg3y6D8O+HSylhVg1xtOiK7l +ElXVRepB8GcX3vf9F58v9s++cSDvXf8vJu/O7nI4fv9C5SfUtMY4JPh/3MTsyl8O +tgxTKjvO +-----END CERTIFICATE----- diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index 8979d5ad29..812706ae1d 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -2229,6 +2229,9 @@ Defines a regular expression which will be used to extract the username from the Allows a specific `UserDetailsService` to be used with X.509 in the case where multiple instances are configured. If not set, an attempt will be made to locate a suitable instance automatically and use that. +[[nsa-x509-extract-principal-name-from-email]] +* **extract-principal-name-from-email** +If true then DN will be extracted from EMAIlADDRESS. [[nsa-filter-chain-map]] == diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java index b690af59c0..ffa217d8d5 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.security.web.authentication.preauth.x509; +import java.security.Principal; import java.security.cert.X509Certificate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,7 +44,9 @@ import org.springframework.util.Assert; * "EMAILADDRESS=jimi@hendrix.org, CN=..." giving a user name "jimi@hendrix.org" * * @author Luke Taylor + * @deprecated Please use {@link SubjectX500PrincipalExtractor} instead */ +@Deprecated public class SubjectDnX509PrincipalExtractor implements X509PrincipalExtractor, MessageSourceAware { protected final Log logger = LogFactory.getLog(getClass()); @@ -59,6 +62,7 @@ public class SubjectDnX509PrincipalExtractor implements X509PrincipalExtractor, @Override public Object extractPrincipal(X509Certificate clientCert) { // String subjectDN = clientCert.getSubjectX500Principal().getName(); + Principal principal = clientCert.getSubjectDN(); String subjectDN = clientCert.getSubjectDN().getName(); this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN)); Matcher matcher = this.subjectDnPattern.matcher(subjectDN); diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java new file mode 100644 index 0000000000..4b735c0528 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.authentication.preauth.x509; + +import java.security.cert.X509Certificate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.security.auth.x500.X500Principal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceAware; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.core.log.LogMessage; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.util.Assert; + +/** + * Obtains the principal from a certificate using RFC2253 and RFC1779 formats. By default, + * RFC2253 is used: DN is extracted from CN. If extractPrincipalNameFromEmail is true then + * format RFC1779 will be used: DN is extracted from EMAIlADDRESS. + * + * @author Max Batischev + * @since 7.0 + */ +public final class SubjectX500PrincipalExtractor implements X509PrincipalExtractor, MessageSourceAware { + + private final Log logger = LogFactory.getLog(getClass()); + + private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + + private boolean extractPrincipalNameFromEmail = false; + + private final Pattern cnSubjectDnPattern = Pattern.compile("CN=(.*?)(?:,|$)", Pattern.CASE_INSENSITIVE); + + private final Pattern emailSubjectDnPattern = Pattern.compile("OID.1.2.840.113549.1.9.1=(.*?)(?:,|$)", + Pattern.CASE_INSENSITIVE); + + @Override + public Object extractPrincipal(X509Certificate clientCert) { + Assert.notNull(clientCert, "clientCert cannot be null"); + X500Principal principal = clientCert.getSubjectX500Principal(); + String subjectDN = this.extractPrincipalNameFromEmail ? principal.getName("RFC1779") : principal.getName(); + this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN)); + Matcher matcher = this.extractPrincipalNameFromEmail ? this.emailSubjectDnPattern.matcher(subjectDN) + : this.cnSubjectDnPattern.matcher(subjectDN); + if (!matcher.find()) { + throw new BadCredentialsException(this.messages.getMessage("SubjectX500PrincipalExtractor.noMatching", + new Object[] { subjectDN }, "No matching pattern was found in subject DN: {0}")); + } + String principalName = matcher.group(1); + this.logger.debug(LogMessage.format("Extracted Principal name is '%s'", principalName)); + return principalName; + } + + @Override + public void setMessageSource(MessageSource messageSource) { + Assert.notNull(messageSource, "messageSource cannot be null"); + this.messages = new MessageSourceAccessor(messageSource); + } + + /** + * If true then DN will be extracted from EMAIlADDRESS, defaults to {@code false} + * @param extractPrincipalNameFromEmail whether to extract DN from EMAIlADDRESS + */ + public void setExtractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) { + this.extractPrincipalNameFromEmail = extractPrincipalNameFromEmail; + } + +} diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java index c49cc6ab2f..02618d126d 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import org.springframework.security.web.authentication.preauth.AbstractPreAuthen */ public class X509AuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter { - private X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); + private X509PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor(); @Override protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { diff --git a/web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java b/web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java new file mode 100644 index 0000000000..ebc1064fdc --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.authentication.preauth.x509; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link SubjectX500PrincipalExtractor}. + * + * @author Max Batischev + */ +public class SubjectX500PrincipalExtractorTests { + + private final SubjectX500PrincipalExtractor extractor = new SubjectX500PrincipalExtractor(); + + @Test + void extractWhenCnPatternSetThenExtractsPrincipalName() throws Exception { + Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificate()); + + assertThat(principal).isEqualTo("Luke Taylor"); + } + + @Test + void extractWhenEmailPatternSetThenExtractsPrincipalName() throws Exception { + this.extractor.setExtractPrincipalNameFromEmail(true); + + Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificate()); + + assertThat(principal).isEqualTo("luke@monkeymachine"); + } + + @Test + void extractWhenCnAtEndThenExtractsPrincipalName() throws Exception { + Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificateWithCnAtEnd()); + + assertThat(principal).isEqualTo("Duke"); + } + + @Test + void setMessageSourceWhenNullThenThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> this.extractor.setMessageSource(null)); + } + + @Test + void extractWhenCertificateIsNullThenFails() { + assertThatIllegalArgumentException().isThrownBy(() -> this.extractor.extractPrincipal(null)); + } + +} From 5f2efbea6a9432c11f2bb0793ea8edf75a907e8b Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:03:20 -0500 Subject: [PATCH 108/149] Remove unused statement --- .../preauth/x509/SubjectDnX509PrincipalExtractor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java index ffa217d8d5..8e77082e3c 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java @@ -16,7 +16,6 @@ package org.springframework.security.web.authentication.preauth.x509; -import java.security.Principal; import java.security.cert.X509Certificate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -62,7 +61,6 @@ public class SubjectDnX509PrincipalExtractor implements X509PrincipalExtractor, @Override public Object extractPrincipal(X509Certificate clientCert) { // String subjectDN = clientCert.getSubjectX500Principal().getName(); - Principal principal = clientCert.getSubjectDN(); String subjectDN = clientCert.getSubjectDN().getName(); this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN)); Matcher matcher = this.subjectDnPattern.matcher(subjectDN); From f690a7f3df302949963311229b6702619c3c7c64 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:41:56 -0500 Subject: [PATCH 109/149] Encapsulate extractPrincipalNameFromEmail property This simplifies the logic when extracting the principal and allows more flexibility in the future by allowing the format and regex to be added as setters. --- .../x509/SubjectX500PrincipalExtractor.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java index 4b735c0528..bb8aa26589 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java @@ -39,29 +39,31 @@ import org.springframework.util.Assert; * format RFC1779 will be used: DN is extracted from EMAIlADDRESS. * * @author Max Batischev + * @author Rob Winch * @since 7.0 */ public final class SubjectX500PrincipalExtractor implements X509PrincipalExtractor, MessageSourceAware { private final Log logger = LogFactory.getLog(getClass()); + private static final Pattern EMAIL_SUBJECT_DN_PATTERN = Pattern.compile("OID.1.2.840.113549.1.9.1=(.*?)(?:,|$)", + Pattern.CASE_INSENSITIVE); + + private static final Pattern CN_SUBJECT_DN_PATTERN = Pattern.compile("CN=(.*?)(?:,|$)", Pattern.CASE_INSENSITIVE); + private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - private boolean extractPrincipalNameFromEmail = false; + private Pattern subjectDnPattern = CN_SUBJECT_DN_PATTERN; - private final Pattern cnSubjectDnPattern = Pattern.compile("CN=(.*?)(?:,|$)", Pattern.CASE_INSENSITIVE); - - private final Pattern emailSubjectDnPattern = Pattern.compile("OID.1.2.840.113549.1.9.1=(.*?)(?:,|$)", - Pattern.CASE_INSENSITIVE); + private String x500PrincipalFormat = X500Principal.RFC2253; @Override public Object extractPrincipal(X509Certificate clientCert) { Assert.notNull(clientCert, "clientCert cannot be null"); X500Principal principal = clientCert.getSubjectX500Principal(); - String subjectDN = this.extractPrincipalNameFromEmail ? principal.getName("RFC1779") : principal.getName(); + String subjectDN = principal.getName(this.x500PrincipalFormat); this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN)); - Matcher matcher = this.extractPrincipalNameFromEmail ? this.emailSubjectDnPattern.matcher(subjectDN) - : this.cnSubjectDnPattern.matcher(subjectDN); + Matcher matcher = this.subjectDnPattern.matcher(subjectDN); if (!matcher.find()) { throw new BadCredentialsException(this.messages.getMessage("SubjectX500PrincipalExtractor.noMatching", new Object[] { subjectDN }, "No matching pattern was found in subject DN: {0}")); @@ -82,7 +84,14 @@ public final class SubjectX500PrincipalExtractor implements X509PrincipalExtract * @param extractPrincipalNameFromEmail whether to extract DN from EMAIlADDRESS */ public void setExtractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) { - this.extractPrincipalNameFromEmail = extractPrincipalNameFromEmail; + if (extractPrincipalNameFromEmail) { + this.subjectDnPattern = EMAIL_SUBJECT_DN_PATTERN; + this.x500PrincipalFormat = X500Principal.RFC1779; + } + else { + this.subjectDnPattern = CN_SUBJECT_DN_PATTERN; + this.x500PrincipalFormat = X500Principal.RFC2253; + } } } From 2b740b7f1fe8130be0c6b3c7624b01d91ae47bf5 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 10 Jun 2025 12:13:10 -0500 Subject: [PATCH 110/149] Update SubjectX500PrincipalExtractor Javadoc - Provide more details on how the principalName is extracted - Update to specify an OID is used for emailAddress --- .../x509/SubjectX500PrincipalExtractor.java | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java index bb8aa26589..9472be2cc7 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java @@ -34,9 +34,10 @@ import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.util.Assert; /** - * Obtains the principal from a certificate using RFC2253 and RFC1779 formats. By default, - * RFC2253 is used: DN is extracted from CN. If extractPrincipalNameFromEmail is true then - * format RFC1779 will be used: DN is extracted from EMAIlADDRESS. + * Extracts the principal from the {@link X500Principal#getName(String)} returned by + * {@link X509Certificate#getSubjectX500Principal()} passed into + * {@link #extractPrincipal(X509Certificate)} depending on the value of + * {@link #setExtractPrincipalNameFromEmail(boolean)}. * * @author Max Batischev * @author Rob Winch @@ -80,8 +81,26 @@ public final class SubjectX500PrincipalExtractor implements X509PrincipalExtract } /** - * If true then DN will be extracted from EMAIlADDRESS, defaults to {@code false} - * @param extractPrincipalNameFromEmail whether to extract DN from EMAIlADDRESS + * Sets if the principal name should be extracted from the emailAddress or CN + * attribute (default). + * + * By default, the format {@link X500Principal#RFC2253} is passed to + * {@link X500Principal#getName(String)} and the principal is extracted from the CN + * attribute as defined in + * Converting + * AttributeTypeAndValue of RFC2253. + * + * If {@link #setExtractPrincipalNameFromEmail(boolean)} is {@code true}, then the + * format {@link X500Principal#RFC2253} is passed to + * {@link X500Principal#getName(String)} and the principal is extracted from the + * OID.1.2.840.113549.1.9.1 + * (emailAddress) attribute as defined in + * Section 2.3 of + * RFC1779. + * @param extractPrincipalNameFromEmail whether to extract the principal from the + * emailAddress (default false) + * @see RFC2253 + * @see RFC1779 */ public void setExtractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) { if (extractPrincipalNameFromEmail) { From 88ed4a5ccfa312fcf8244c82ffbe727935738d84 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 10 Jun 2025 15:22:20 -0500 Subject: [PATCH 111/149] Use principalExtractor reference instead of properties --- .../web/configurers/X509Configurer.java | 27 +--- .../http/AuthenticationConfigBuilder.java | 16 +-- .../security/config/annotation/web/X509Dsl.kt | 1 + .../security/config/spring-security-7.0.rnc | 3 - .../security/config/spring-security-7.0.xsd | 6 - .../web/configurers/X509ConfigurerTests.java | 119 ++++++++++++------ config/src/test/resources/max.cer | 17 --- .../servlet/appendix/namespace/http.adoc | 3 - 8 files changed, 85 insertions(+), 107 deletions(-) delete mode 100644 config/src/test/resources/max.cer diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java index b9b766a14d..d6241a0fbe 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java @@ -33,7 +33,6 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails; import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; -import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; @@ -75,7 +74,6 @@ import org.springframework.security.web.context.RequestAttributeSecurityContextR * * @author Rob Winch * @author Ngoc Nhan - * @author Max Batischev * @since 3.2 */ public final class X509Configurer> @@ -163,38 +161,17 @@ public final class X509Configurer> * @param subjectPrincipalRegex the regex to extract the user principal from the * certificate (i.e. "CN=(.*?)(?:,|$)"). * @return the {@link X509Configurer} for further customizations - * @deprecated Please use {{@link #extractPrincipalNameFromEmail(boolean)}} instead + * @deprecated Please use {{@link #x509PrincipalExtractor(X509PrincipalExtractor)} + * instead */ @Deprecated public X509Configurer subjectPrincipalRegex(String subjectPrincipalRegex) { - if (this.x509PrincipalExtractor instanceof SubjectX500PrincipalExtractor) { - throw new IllegalStateException( - "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " - + "Please use one or the other."); - } SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); principalExtractor.setSubjectDnRegex(subjectPrincipalRegex); this.x509PrincipalExtractor = principalExtractor; return this; } - /** - * If true then DN will be extracted from EMAIlADDRESS, defaults to {@code false} - * @param extractPrincipalNameFromEmail whether to extract DN from EMAIlADDRESS - * @since 7.0 - */ - public X509Configurer extractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) { - if (this.x509PrincipalExtractor instanceof SubjectDnX509PrincipalExtractor) { - throw new IllegalStateException( - "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " - + "Please use one or the other."); - } - SubjectX500PrincipalExtractor extractor = new SubjectX500PrincipalExtractor(); - extractor.setExtractPrincipalNameFromEmail(extractPrincipalNameFromEmail); - this.x509PrincipalExtractor = extractor; - return this; - } - @Override public void init(H http) { PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider(); diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java index 9b6ee210ac..f997e5fa5e 100644 --- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2025 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. @@ -57,7 +57,6 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedG import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource; import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter; import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; -import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter; @@ -523,25 +522,12 @@ final class AuthenticationConfigBuilder { filterBuilder.addPropertyValue("securityContextHolderStrategy", authenticationFilterSecurityContextHolderStrategyRef); String regex = x509Elt.getAttribute("subject-principal-regex"); - String extractPrincipalNameFromEmail = x509Elt.getAttribute("extract-principal-name-from-email"); - if (StringUtils.hasText(regex) && StringUtils.hasText(extractPrincipalNameFromEmail)) { - throw new IllegalStateException( - "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " - + "Please use one or the other."); - } if (StringUtils.hasText(regex)) { BeanDefinitionBuilder extractor = BeanDefinitionBuilder .rootBeanDefinition(SubjectDnX509PrincipalExtractor.class); extractor.addPropertyValue("subjectDnRegex", regex); filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition()); } - if (StringUtils.hasText(extractPrincipalNameFromEmail)) { - BeanDefinitionBuilder extractor = BeanDefinitionBuilder - .rootBeanDefinition(SubjectX500PrincipalExtractor.class); - extractor.addPropertyValue("extractPrincipalNameFromEmail", - Boolean.parseBoolean(extractPrincipalNameFromEmail)); - filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition()); - } injectAuthenticationDetailsSource(x509Elt, filterBuilder); filter = (RootBeanDefinition) filterBuilder.getBeanDefinition(); createPrauthEntryPoint(x509Elt); diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/X509Dsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/X509Dsl.kt index f36897604a..514ace50b3 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/X509Dsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/X509Dsl.kt @@ -51,6 +51,7 @@ class X509Dsl { var authenticationDetailsSource: AuthenticationDetailsSource? = null var userDetailsService: UserDetailsService? = null var authenticationUserDetailsService: AuthenticationUserDetailsService? = null + @Deprecated("Use x509PrincipalExtractor instead") var subjectPrincipalRegex: String? = null diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc index 1236a2aec5..bbf8622dfe 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc @@ -1053,9 +1053,6 @@ x509.attlist &= x509.attlist &= ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter attribute authentication-details-source-ref {xsd:token}? -x509.attlist &= - ## If true then DN will be extracted from EMAIlADDRESS - attribute extract-principal-name-from-email {xsd:token}? jee = ## Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication. diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd index ed82f3468b..2e3d6cf275 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd @@ -2917,12 +2917,6 @@ - - - If true then DN will be extracted from EMAIlADDRESS - - - diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java index 38983691e1..63a3a963df 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2025 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. @@ -43,7 +43,9 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; +import org.springframework.security.web.authentication.preauth.x509.X509TestUtils; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; @@ -123,16 +125,6 @@ public class X509ConfigurerTests { // @formatter:on } - @Test - public void x509WhenExtractPrincipalNameFromEmailIsTrueThenUsesEmailAddressToExtractPrincipal() throws Exception { - this.spring.register(EmailPrincipalConfig.class).autowire(); - X509Certificate certificate = loadCert("max.cer"); - // @formatter:off - this.mvc.perform(get("/").with(x509(certificate))) - .andExpect(authenticated().withUsername("maxbatischev@gmail.com")); - // @formatter:on - } - @Test public void x509WhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception { this.spring.register(UserDetailsServiceBeanConfig.class).autowire(); @@ -165,6 +157,28 @@ public class X509ConfigurerTests { // @formatter:on } + @Test + public void x509WhenSubjectX500PrincipalExtractor() throws Exception { + this.spring.register(SubjectX500PrincipalExtractorConfig.class).autowire(); + X509Certificate certificate = loadCert("rod.cer"); + // @formatter:off + this.mvc.perform(get("/").with(x509(certificate))) + .andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull()) + .andExpect(authenticated().withUsername("rod")); + // @formatter:on + } + + @Test + public void x509WhenSubjectX500PrincipalExtractorBean() throws Exception { + this.spring.register(SubjectX500PrincipalExtractorEmailConfig.class).autowire(); + X509Certificate certificate = X509TestUtils.buildTestCertificate(); + // @formatter:off + this.mvc.perform(get("/").with(x509(certificate))) + .andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull()) + .andExpect(authenticated().withUsername("luke@monkeymachine")); + // @formatter:on + } + private T loadCert(String location) { try (InputStream is = new ClassPathResource(location).getInputStream()) { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); @@ -287,33 +301,6 @@ public class X509ConfigurerTests { } - @Configuration - @EnableWebSecurity - static class EmailPrincipalConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .x509((x509) -> - x509.extractPrincipalNameFromEmail(true) - ); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("maxbatischev@gmail.com") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - } - @Configuration @EnableWebSecurity static class UserDetailsServiceBeanConfig { @@ -397,4 +384,60 @@ public class X509ConfigurerTests { } + @Configuration + @EnableWebSecurity + static class SubjectX500PrincipalExtractorConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .x509((x509) -> x509 + .x509PrincipalExtractor(new SubjectX500PrincipalExtractor()) + ); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService userDetailsService() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("rod") + .password("password") + .roles("USER", "ADMIN") + .build(); + return new InMemoryUserDetailsManager(user); + } + + } + + @Configuration + @EnableWebSecurity + static class SubjectX500PrincipalExtractorEmailConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor(); + principalExtractor.setExtractPrincipalNameFromEmail(true); + // @formatter:off + http + .x509((x509) -> x509 + .x509PrincipalExtractor(principalExtractor) + ); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService userDetailsService() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("luke@monkeymachine") + .password("password") + .roles("USER", "ADMIN") + .build(); + return new InMemoryUserDetailsManager(user); + } + + } + } diff --git a/config/src/test/resources/max.cer b/config/src/test/resources/max.cer deleted file mode 100644 index bd79b1f096..0000000000 --- a/config/src/test/resources/max.cer +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICojCCAgugAwIBAgIBADANBgkqhkiG9w0BAQ0FADBuMQswCQYDVQQGEwJydTEP -MA0GA1UECAwGTW9zY293MQ8wDQYDVQQKDAZTcHJpbmcxFjAUBgNVBAMMDU1heCBC -YXRpc2NoZXYxJTAjBgkqhkiG9w0BCQEWFm1heGJhdGlzY2hldkBnbWFpbC5jb20w -HhcNMjUwNTE0MTcyODM5WhcNMjYwNTE0MTcyODM5WjBuMQswCQYDVQQGEwJydTEP -MA0GA1UECAwGTW9zY293MQ8wDQYDVQQKDAZTcHJpbmcxFjAUBgNVBAMMDU1heCBC -YXRpc2NoZXYxJTAjBgkqhkiG9w0BCQEWFm1heGJhdGlzY2hldkBnbWFpbC5jb20w -gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALVZ2K/iOINeHZ4XAV3QmNRgS+iB -Vw0fW07uzYkCoSZU1lOBQE0k8+fdM2+X9AsgwfRCE3tUZquPApEKynB5V9Seh+bR -vc9aj7PunMyN+zjRU6X7/BL3VqLfrJLSc15bQaSN1phJ6NT+BTXPTuiPbXldnJLC -wVo6PView83yZ335AgMBAAGjUDBOMB0GA1UdDgQWBBQhyQfxL2ZYotcS8AmMJtli -2IRAMTAfBgNVHSMEGDAWgBQhyQfxL2ZYotcS8AmMJtli2IRAMTAMBgNVHRMEBTAD -AQH/MA0GCSqGSIb3DQEBDQUAA4GBAIIIJxpsTPtUEnePAqqgVFWDKC2CExhtCBYL -MjLSC+7E9OlfuuX1joAsD4Yv86k4Ox836D0KQtINtg3y6D8O+HSylhVg1xtOiK7l -ElXVRepB8GcX3vf9F58v9s++cSDvXf8vJu/O7nI4fv9C5SfUtMY4JPh/3MTsyl8O -tgxTKjvO ------END CERTIFICATE----- diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index 812706ae1d..8979d5ad29 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -2229,9 +2229,6 @@ Defines a regular expression which will be used to extract the username from the Allows a specific `UserDetailsService` to be used with X.509 in the case where multiple instances are configured. If not set, an attempt will be made to locate a suitable instance automatically and use that. -[[nsa-x509-extract-principal-name-from-email]] -* **extract-principal-name-from-email** -If true then DN will be extracted from EMAIlADDRESS. [[nsa-filter-chain-map]] == From 7bf2730a5359b2a28b6de825cd7ca138ba85cfa0 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 11 Jun 2025 13:05:43 -0500 Subject: [PATCH 112/149] Add x509@principal-extractor-ref Enables customizing the X500PrincipalExtractor --- .../http/AuthenticationConfigBuilder.java | 18 ++++++-- .../security/config/spring-security-7.0.rnc | 3 ++ .../security/config/spring-security-7.0.xsd | 6 +++ .../config/http/MiscHttpConfigTests.java | 23 +++++++++++ ...pConfigTests-X509PrincipalExtractorRef.xml | 41 +++++++++++++++++++ ...alExtractorRefAndSubjectPrincipalRegex.xml | 41 +++++++++++++++++++ .../servlet/appendix/namespace/http.adoc | 4 ++ 7 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRef.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRefAndSubjectPrincipalRegex.xml diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java index f997e5fa5e..55efc57a17 100644 --- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java @@ -521,11 +521,23 @@ final class AuthenticationConfigBuilder { filterBuilder.addPropertyValue("authenticationManager", authManager); filterBuilder.addPropertyValue("securityContextHolderStrategy", authenticationFilterSecurityContextHolderStrategyRef); - String regex = x509Elt.getAttribute("subject-principal-regex"); - if (StringUtils.hasText(regex)) { + String principalExtractorRef = x509Elt.getAttribute("principal-extractor-ref"); + String subjectPrincipalRegex = x509Elt.getAttribute("subject-principal-regex"); + boolean hasPrincipalExtractorRef = StringUtils.hasText(principalExtractorRef); + boolean hasSubjectPrincipalRegex = StringUtils.hasText(subjectPrincipalRegex); + if (hasPrincipalExtractorRef && hasSubjectPrincipalRegex) { + this.pc.getReaderContext() + .error("The attribute 'principal-extractor-ref' cannot be used together with the 'subject-principal-regex' attribute within <" + + Elements.X509 + ">", this.pc.extractSource(x509Elt)); + } + if (hasPrincipalExtractorRef) { + RuntimeBeanReference principalExtractor = new RuntimeBeanReference(principalExtractorRef); + filterBuilder.addPropertyValue("principalExtractor", principalExtractor); + } + if (hasSubjectPrincipalRegex) { BeanDefinitionBuilder extractor = BeanDefinitionBuilder .rootBeanDefinition(SubjectDnX509PrincipalExtractor.class); - extractor.addPropertyValue("subjectDnRegex", regex); + extractor.addPropertyValue("subjectDnRegex", subjectPrincipalRegex); filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition()); } injectAuthenticationDetailsSource(x509Elt, filterBuilder); diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc index bbf8622dfe..d9a8d4f9d5 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc @@ -1053,6 +1053,9 @@ x509.attlist &= x509.attlist &= ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter attribute authentication-details-source-ref {xsd:token}? +x509.attlist &= + ## Reference to an X509PrincipalExtractor which will be used by the authentication filter + attribute principal-extractor-ref {xsd:token}? jee = ## Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication. diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd index 2e3d6cf275..4ff414800b 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd @@ -2917,6 +2917,12 @@ + + + Reference to an X509PrincipalExtractor which will be used by the authentication filter + + + diff --git a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java index 5221350cde..10927906ea 100644 --- a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.OutputStream; import java.security.AccessController; import java.security.Principal; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -91,6 +92,7 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; +import org.springframework.security.web.authentication.preauth.x509.X509TestUtils; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; @@ -398,6 +400,27 @@ public class MiscHttpConfigTests { .containsSubsequence(CsrfFilter.class, X509AuthenticationFilter.class, ExceptionTranslationFilter.class); } + @Test + public void getWhenUsingX509PrincipalExtractorRef() throws Exception { + this.spring.configLocations(xml("X509PrincipalExtractorRef")).autowire(); + X509Certificate certificate = X509TestUtils.buildTestCertificate(); + RequestPostProcessor x509 = x509(certificate); + // @formatter:off + this.mvc.perform(get("/protected").with(x509)) + .andExpect(status().isOk()); + // @formatter:on + } + + @Test + public void getWhenUsingX509PrincipalExtractorRefAndSubjectPrincipalRegex() throws Exception { + String xmlResourceName = "X509PrincipalExtractorRefAndSubjectPrincipalRegex"; + // @formatter:off + assertThatExceptionOfType(BeanDefinitionParsingException.class) + .isThrownBy(() -> this.spring.configLocations(xml(xmlResourceName)).autowire()) + .withMessage("Configuration problem: The attribute 'principal-extractor-ref' cannot be used together with the 'subject-principal-regex' attribute within \n" + "Offending resource: class path resource [org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRefAndSubjectPrincipalRegex.xml]"); + // @formatter:on + } + @Test public void getWhenUsingX509AndPropertyPlaceholderThenSubjectPrincipalRegexIsConfigured() throws Exception { System.setProperty("subject_principal_regex", "OU=(.*?)(?:,|$)"); diff --git a/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRef.xml b/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRef.xml new file mode 100644 index 0000000000..51a3a1bbf1 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRef.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRefAndSubjectPrincipalRegex.xml b/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRefAndSubjectPrincipalRegex.xml new file mode 100644 index 0000000000..1fc7e0f12c --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRefAndSubjectPrincipalRegex.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index 8979d5ad29..555a1b4323 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -2218,6 +2218,10 @@ A `PreAuthenticatedAuthenticationProvider` will also be created which delegates * **authentication-details-source-ref** A reference to an `AuthenticationDetailsSource` +[[nsa-x509-principal-extractor-ref]] +* **principal-extractor-ref** +Reference to an `X509PrincipalExtractor` which will be used by the authentication filter. + [[nsa-x509-subject-principal-regex]] * **subject-principal-regex** From e3add59550f516111f755c7f4591bb501c05fdd5 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 11 Jun 2025 08:41:38 -0500 Subject: [PATCH 113/149] Update x509 Reference - Use include-code - Demo how to customize SubjectX500PrincipalExtractor --- docs/antora.yml | 1 + .../pages/reactive/authentication/x509.adoc | 94 +---------- .../pages/servlet/authentication/x509.adoc | 42 ++--- .../reactivex509/CustomX509Configuration.java | 74 +++++++++ .../DefaultX509Configuration.java | 67 ++++++++ .../reactivex509/X509ConfigurationTests.java | 148 ++++++++++++++++++ .../CustomX509Configuration.java | 75 +++++++++ .../DefaultX509Configuration.java | 67 ++++++++ .../X509ConfigurationTests.java | 103 ++++++++++++ .../reactivex509/CustomX509Configuration.kt | 74 +++++++++ .../reactivex509/DefaultX509Configuration.kt | 64 ++++++++ .../reactivex509/X509ConfigurationTests.kt | 131 ++++++++++++++++ .../CustomX509Configuration.kt | 69 ++++++++ .../DefaultX509Configuration.kt | 64 ++++++++ .../X509ConfigurationTests.kt | 75 +++++++++ .../CustomX509Configuration.xml | 45 ++++++ .../DefaultX509Configuration.xml | 39 +++++ 17 files changed, 1118 insertions(+), 114 deletions(-) create mode 100644 docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/CustomX509Configuration.java create mode 100644 docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/DefaultX509Configuration.java create mode 100644 docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java create mode 100644 docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.java create mode 100644 docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.java create mode 100644 docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/X509ConfigurationTests.java create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/CustomX509Configuration.kt create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/DefaultX509Configuration.kt create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/CustomX509Configuration.kt create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/DefaultX509Configuration.kt create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/X509ConfigurationTests.kt create mode 100644 docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.xml create mode 100644 docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.xml diff --git a/docs/antora.yml b/docs/antora.yml index 02262db72e..c6a145aaab 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -18,3 +18,4 @@ asciidoc: gh-url: "https://github.com/spring-projects/spring-security/tree/{gh-tag}" include-java: 'example$docs-src/test/java/org/springframework/security/docs' include-kotlin: 'example$docs-src/test/kotlin/org/springframework/security/kt/docs' + include-xml: 'example$docs-src/test/resources/org/springframework/security/docs' diff --git a/docs/modules/ROOT/pages/reactive/authentication/x509.adoc b/docs/modules/ROOT/pages/reactive/authentication/x509.adoc index a077dd5c65..947084423c 100644 --- a/docs/modules/ROOT/pages/reactive/authentication/x509.adoc +++ b/docs/modules/ROOT/pages/reactive/authentication/x509.adoc @@ -5,98 +5,16 @@ Similar to xref:servlet/authentication/x509.adoc#servlet-x509[Servlet X.509 auth The following example shows a reactive x509 security configuration: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - http - .x509(withDefaults()) - .authorizeExchange(exchanges -> exchanges - .anyExchange().permitAll() - ); - return http.build(); -} ----- +include-code::./DefaultX509Configuration[tag=springSecurity,indent=0] -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { - x509 { } - authorizeExchange { - authorize(anyExchange, authenticated) - } - } -} ----- -====== - -In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used. The default principal extractor is `SubjectDnX509PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client. The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired. +In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used. +The default principal extractor is `SubjectX500PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client. +The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired. The following example demonstrates how these defaults can be overridden: -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - SubjectDnX509PrincipalExtractor principalExtractor = - new SubjectDnX509PrincipalExtractor(); +include-code::./CustomX509Configuration[tag=springSecurity,indent=0] - principalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)"); - - ReactiveAuthenticationManager authenticationManager = authentication -> { - authentication.setAuthenticated("Trusted Org Unit".equals(authentication.getName())); - return Mono.just(authentication); - }; - - http - .x509(x509 -> x509 - .principalExtractor(principalExtractor) - .authenticationManager(authenticationManager) - ) - .authorizeExchange(exchanges -> exchanges - .anyExchange().authenticated() - ); - return http.build(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { - val customPrincipalExtractor = SubjectDnX509PrincipalExtractor() - customPrincipalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)") - val customAuthenticationManager = ReactiveAuthenticationManager { authentication: Authentication -> - authentication.isAuthenticated = "Trusted Org Unit" == authentication.name - Mono.just(authentication) - } - return http { - x509 { - principalExtractor = customPrincipalExtractor - authenticationManager = customAuthenticationManager - } - authorizeExchange { - authorize(anyExchange, authenticated) - } - } -} ----- -====== - -In the previous example, a username is extracted from the OU field of a client certificate instead of CN, and account lookup using `ReactiveUserDetailsService` is not performed at all. Instead, if the provided certificate issued to an OU named "`Trusted Org Unit`", a request is authenticated. +In the previous example, a username is extracted from the `emailAddress` field of a client certificate instead of CN, and account lookup uses a custom `ReactiveAuthenticationManager` instance. For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, see https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509. diff --git a/docs/modules/ROOT/pages/servlet/authentication/x509.adoc b/docs/modules/ROOT/pages/servlet/authentication/x509.adoc index 26dbb1a900..2d670a632e 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/x509.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/x509.adoc @@ -14,37 +14,27 @@ You should get this working before trying it out with Spring Security. The Spring Security X.509 module extracts the certificate by using a filter. It maps the certificate to an application user and loads that user's set of granted authorities for use with the standard Spring Security infrastructure. - +[[servlet-x509-config]] == Adding X.509 Authentication to Your Web Application -Enabling X.509 client authentication is very straightforward. -To do so, add the `` element to your http security namespace configuration: -[source,xml] ----- - -... - ; - ----- +Similar to xref:reactive/authentication/x509.adoc[Reactive X.509 authentication], the servlet x509 authentication filter allows extracting an authentication token from a certificate provided by a client. -The element has two optional attributes: +The following example shows a reactive x509 security configuration: -* `subject-principal-regex`. -The regular expression used to extract a username from the certificate's subject name. -The default value is shown in the preceding listing. -This is the username that is passed to the `UserDetailsService` to load the authorities for the user. -* `user-service-ref`. -This is the bean ID of the `UserDetailsService` to be used with X.509. -It is not needed if there is only one defined in your application context. +include-code::./DefaultX509Configuration[tag=springSecurity,indent=0] + +In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used. +The default principal extractor is `SubjectX500PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client. +The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired. + +The following example demonstrates how these defaults can be overridden: + +include-code::./CustomX509Configuration[tag=springSecurity,indent=0] + +In the previous example, a username is extracted from the `emailAddress` field of a client certificate instead of CN, and account lookup uses a custom `ReactiveAuthenticationManager` instance. + +For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, see https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509. -The `subject-principal-regex` should contain a single group. -For example, the default expression (`CN=(.*?)`) matches the common name field. -So, if the subject name in the certificate is "CN=Jimi Hendrix, OU=...", this gives a user name of "Jimi Hendrix". -The matches are case insensitive. -So "emailAddress=(+.*?+)," matches "EMAILADDRESS=jimi@hendrix.org,CN=...", giving a user name "jimi@hendrix.org". -If the client presents a certificate and a valid username is successfully extracted, there should be a valid `Authentication` object in the security context. -If no certificate is found or no corresponding user could be found, the security context remains empty. -This means that you can use X.509 authentication with other options, such as a form-based login. [[x509-ssl-config]] == Setting up SSL in Tomcat diff --git a/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/CustomX509Configuration.java b/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/CustomX509Configuration.java new file mode 100644 index 0000000000..8f7f8c8278 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/CustomX509Configuration.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.reactive.authentication.reactivex509; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.ReactiveAuthenticationManager; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager; +import org.springframework.web.reactive.config.EnableWebFlux; + +/** + * Demonstrates custom configuration for x509 reactive configuration. + * + * @author Rob Winch + */ +@Configuration(proxyBeanMethods = false) +@EnableWebFluxSecurity +@EnableWebFlux +public class CustomX509Configuration { + + // tag::springSecurity[] + @Bean + SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { + SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor(); + principalExtractor.setExtractPrincipalNameFromEmail(true); + + // @formatter:off + UserDetails user = User + .withUsername("luke@monkeymachine") + .password("password") + .roles("USER") + .build(); + // @formatter:on + + ReactiveUserDetailsService users = new MapReactiveUserDetailsService(user); + ReactiveAuthenticationManager authenticationManager = new ReactivePreAuthenticatedAuthenticationManager(users); + + // @formatter:off + http + .x509(x509 -> x509 + .principalExtractor(principalExtractor) + .authenticationManager(authenticationManager) + ) + .authorizeExchange(exchanges -> exchanges + .anyExchange().authenticated() + ); + // @formatter:on + return http.build(); + } + // end::springSecurity[] + +} diff --git a/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/DefaultX509Configuration.java b/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/DefaultX509Configuration.java new file mode 100644 index 0000000000..0fd17fa1d8 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/DefaultX509Configuration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.reactive.authentication.reactivex509; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.web.reactive.config.EnableWebFlux; + +/** + * Demonstrates custom configuration for x509 reactive configuration. + * + * @author Rob Winch + */ +@Configuration(proxyBeanMethods = false) +@EnableWebFluxSecurity +@EnableWebFlux +public class DefaultX509Configuration { + + // tag::springSecurity[] + @Bean + SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { + // @formatter:off + http + .x509(Customizer.withDefaults()) + .authorizeExchange(exchanges -> exchanges + .anyExchange().authenticated() + ); + // @formatter:on + return http.build(); + } + // end::springSecurity[] + + @Bean + ReactiveUserDetailsService userDetailsService() { + // @formatter:off + UserDetails user = User + .withUsername("rod") + .password("password") + .roles("USER") + .build(); + // @formatter:on + + return new MapReactiveUserDetailsService(user); + } +} diff --git a/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java b/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java new file mode 100644 index 0000000000..475787da4f --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.reactive.authentication.reactivex509; + +import java.io.InputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; +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.core.io.ClassPathResource; +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.SslInfo; +import org.springframework.security.config.test.SpringTestContext; +import org.springframework.security.config.test.SpringTestContextExtension; +import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; +import org.springframework.security.web.authentication.preauth.x509.X509TestUtils; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClientConfigurer; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; + +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +/** + * Tests {@link CustomX509Configuration}. + * + * @author Rob Winch + */ +@ExtendWith(SpringTestContextExtension.class) +public class X509ConfigurationTests { + + public final SpringTestContext spring = new SpringTestContext(this); + + WebTestClient client; + + @Autowired + void setSpringSecurityFilterChain(WebFilter springSecurityFilterChain) { + this.client = WebTestClient.bindToController(WebTestClientBuilder.Http200RestController.class) + .webFilter(springSecurityFilterChain) + .apply(springSecurity()) + .configureClient() + .build(); + } + + @Test + void x509WhenDefaultX509Configuration() throws Exception { + this.spring.register(DefaultX509Configuration.class).autowire(); + X509Certificate certificate = loadCert("rod.cer"); + // @formatter:off + this.client + .mutateWith(x509(certificate)) + .get() + .uri("/") + .exchange() + .expectStatus().isOk(); + // @formatter:on + } + + @Test + void x509WhenCustomX509Configuration() throws Exception { + this.spring.register(CustomX509Configuration.class).autowire(); + X509Certificate certificate = X509TestUtils.buildTestCertificate(); + // @formatter:off + this.client + .mutateWith(x509(certificate)) + .get() + .uri("/") + .exchange() + .expectStatus().isOk(); + // @formatter:on + } + + private static @NotNull WebTestClientConfigurer x509(X509Certificate certificate) { + return (builder, httpHandlerBuilder, connector) -> { + builder.apply(new WebTestClientConfigurer() { + @Override + public void afterConfigurerAdded(WebTestClient.Builder builder, + @Nullable WebHttpHandlerBuilder httpHandlerBuilder, + @Nullable ClientHttpConnector connector) { + SslInfo sslInfo = new SslInfo() { + @Override + public @Nullable String getSessionId() { + return "sessionId"; + } + + @Override + public X509Certificate @Nullable [] getPeerCertificates() { + return new X509Certificate[] { certificate }; + } + }; + httpHandlerBuilder.filters((filters) -> filters.add(0, new SslInfoOverrideWebFilter(sslInfo))); + } + }); + }; + } + + private static class SslInfoOverrideWebFilter implements WebFilter { + private final SslInfo sslInfo; + + private SslInfoOverrideWebFilter(SslInfo sslInfo) { + this.sslInfo = sslInfo; + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + ServerHttpRequest sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo) + .build(); + ServerWebExchange sslInfoExchange = exchange.mutate().request(sslInfoRequest).build(); + return chain.filter(sslInfoExchange); + } + } + + private T loadCert(String location) { + try (InputStream is = new ClassPathResource(location).getInputStream()) { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (T) certFactory.generateCertificate(is); + } + catch (Exception ex) { + throw new IllegalArgumentException(ex); + } + } +} diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.java new file mode 100644 index 0000000000..70b46b7c54 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.servlet.authentication.servletx509config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +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.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * Demonstrates custom configuration for x509 reactive configuration. + * + * @author Rob Winch + */ +@EnableWebMvc +@EnableWebSecurity +@Configuration(proxyBeanMethods = false) +public class CustomX509Configuration { + + // tag::springSecurity[] + @Bean + DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception { + SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor(); + principalExtractor.setExtractPrincipalNameFromEmail(true); + + + // @formatter:off + http + .x509((x509) -> x509 + .x509PrincipalExtractor(principalExtractor) + ) + .authorizeHttpRequests((exchanges) -> exchanges + .anyRequest().authenticated() + ); + // @formatter:on + return http.build(); + } + // end::springSecurity[] + + @Bean + UserDetailsService userDetailsService() { + // @formatter:off + UserDetails user = User + .withUsername("luke@monkeymachine") + .password("password") + .roles("USER") + .build(); + // @formatter:on + + return new InMemoryUserDetailsManager(user); + } +} diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.java new file mode 100644 index 0000000000..a347235386 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.servlet.authentication.servletx509config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.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.web.DefaultSecurityFilterChain; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * Demonstrates custom configuration for x509 reactive configuration. + * + * @author Rob Winch + */ +@EnableWebMvc +@EnableWebSecurity +@Configuration(proxyBeanMethods = false) +public class DefaultX509Configuration { + + // tag::springSecurity[] + @Bean + DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception { + // @formatter:off + http + .x509(Customizer.withDefaults()) + .authorizeHttpRequests(exchanges -> exchanges + .anyRequest().authenticated() + ); + // @formatter:on + return http.build(); + } + // end::springSecurity[] + + @Bean + UserDetailsService userDetailsService() { + // @formatter:off + UserDetails user = User + .withUsername("rod") + .password("password") + .roles("USER") + .build(); + // @formatter:on + + return new InMemoryUserDetailsManager(user); + } +} diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/X509ConfigurationTests.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/X509ConfigurationTests.java new file mode 100644 index 0000000000..c6aa6f6d41 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/X509ConfigurationTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.servlet.authentication.servletx509config; + +import java.io.InputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; +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.core.io.ClassPathResource; +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.SslInfo; +import org.springframework.security.config.test.SpringTestContext; +import org.springframework.security.config.test.SpringTestContextExtension; +import org.springframework.security.web.authentication.preauth.x509.X509TestUtils; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClientConfigurer; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests {@link CustomX509Configuration}. + * + * @author Rob Winch + */ +@ExtendWith(SpringTestContextExtension.class) +public class X509ConfigurationTests { + + public final SpringTestContext spring = new SpringTestContext(this); + + @Autowired + MockMvc mockMvc; + + @Test + void x509WhenDefaultX509Configuration() throws Exception { + this.spring.register(DefaultX509Configuration.class, Http200Controller.class).autowire(); + // @formatter:off + this.mockMvc.perform(get("/").with(x509("rod.cer"))) + .andExpect(status().isOk()) + .andExpect(authenticated().withUsername("rod")); + // @formatter:on + } + + @Test + void x509WhenDefaultX509ConfigurationXml() throws Exception { + this.spring.testConfigLocations("DefaultX509Configuration.xml").autowire(); + // @formatter:off + this.mockMvc.perform(get("/").with(x509("rod.cer"))) + .andExpect(authenticated().withUsername("rod")); + // @formatter:on + } + + @Test + void x509WhenCustomX509Configuration() throws Exception { + this.spring.register(CustomX509Configuration.class, Http200Controller.class).autowire(); + X509Certificate certificate = X509TestUtils.buildTestCertificate(); + // @formatter:off + this.mockMvc.perform(get("/").with(x509(certificate))) + .andExpect(status().isOk()) + .andExpect(authenticated().withUsername("luke@monkeymachine")); + // @formatter:on + } + + @RestController + static class Http200Controller { + @GetMapping("/**") + String ok() { + return "ok"; + } + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/CustomX509Configuration.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/CustomX509Configuration.kt new file mode 100644 index 0000000000..78c4fbe34b --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/CustomX509Configuration.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.kt.docs.reactive.authentication.reactivex509 + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.authentication.ReactiveAuthenticationManager +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity +import org.springframework.security.config.web.server.invoke +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.config.web.server.ServerHttpSecurity.http +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService +import org.springframework.security.core.userdetails.ReactiveUserDetailsService +import org.springframework.security.core.userdetails.User +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor +import org.springframework.security.web.server.SecurityWebFilterChain +import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager +import org.springframework.web.reactive.config.EnableWebFlux + +/** + * Demonstrates custom configuration for x509 reactive configuration. + * + * @author Rob Winch + */ +@EnableWebFlux +@EnableWebFluxSecurity +@Configuration(proxyBeanMethods = false) +class CustomX509Configuration { + + // tag::springSecurity[] + @Bean + fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { + val extractor = SubjectX500PrincipalExtractor() + extractor.setExtractPrincipalNameFromEmail(true) + + // @formatter:off + val user = User + .withUsername("luke@monkeymachine") + .password("password") + .roles("USER") + .build() + // @formatter:on + + val users: ReactiveUserDetailsService = MapReactiveUserDetailsService(user) + val authentication: ReactiveAuthenticationManager = ReactivePreAuthenticatedAuthenticationManager(users) + + return http { + x509 { + principalExtractor = extractor + authenticationManager = authentication + } + authorizeExchange { + authorize(anyExchange, authenticated) + } + } + } + // end::springSecurity[] + + +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/DefaultX509Configuration.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/DefaultX509Configuration.kt new file mode 100644 index 0000000000..61e5d65db5 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/DefaultX509Configuration.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.kt.docs.reactive.authentication.reactivex509 + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.config.web.server.invoke +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService +import org.springframework.security.core.userdetails.User +import org.springframework.security.web.server.SecurityWebFilterChain +import org.springframework.web.reactive.config.EnableWebFlux + +/** + * Demonstrates custom configuration for x509 reactive configuration. + * + * @author Rob Winch + */ +@EnableWebFlux +@EnableWebFluxSecurity +@Configuration(proxyBeanMethods = false) +class DefaultX509Configuration { + + // tag::springSecurity[] + @Bean + fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { + return http { + x509 { } + authorizeExchange { + authorize(anyExchange, authenticated) + } + } + } + // end::springSecurity[] + + @Bean + fun userDetailsService(): MapReactiveUserDetailsService { + // @formatter:off + val user = User + .withUsername("rod") + .password("password") + .roles("USER") + .build() + // @formatter:on + + return MapReactiveUserDetailsService(user) + } + +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt new file mode 100644 index 0000000000..4802660f06 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt @@ -0,0 +1,131 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.kt.docs.reactive.authentication.reactivex509 + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.core.io.ClassPathResource +import org.springframework.http.client.reactive.ClientHttpConnector +import org.springframework.http.server.reactive.SslInfo +import org.springframework.security.config.test.SpringTestContext +import org.springframework.security.config.test.SpringTestContextExtension +import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers +import org.springframework.security.test.web.reactive.server.WebTestClientBuilder.Http200RestController +import org.springframework.security.web.authentication.preauth.x509.X509TestUtils +import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.reactive.server.WebTestClientConfigurer +import org.springframework.web.server.ServerWebExchange +import org.springframework.web.server.WebFilter +import org.springframework.web.server.WebFilterChain +import org.springframework.web.server.adapter.WebHttpHandlerBuilder +import reactor.core.publisher.Mono +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.util.function.Consumer + +/** + * Tests [CustomX509Configuration]. + * + * @author Rob Winch + */ +@ExtendWith(SpringTestContextExtension::class) +class X509ConfigurationTests { + @JvmField + val spring: SpringTestContext = SpringTestContext(this) + + var client: WebTestClient? = null + + @Autowired + fun setSpringSecurityFilterChain(springSecurityFilterChain: WebFilter) { + this.client = WebTestClient + .bindToController(Http200RestController::class.java) + .webFilter(springSecurityFilterChain) + .apply(SecurityMockServerConfigurers.springSecurity()) + .configureClient() + .build() + } + + @Test + fun x509WhenDefaultX509Configuration() { + this.spring.register(DefaultX509Configuration::class.java).autowire() + val certificate = loadCert("rod.cer") + // @formatter:off + this.client!!.mutateWith(x509(certificate)) + .get() + .uri("/") + .exchange() + .expectStatus().isOk() + // @formatter:on + } + + @Test + fun x509WhenCustomX509Configuration() { + this.spring.register(CustomX509Configuration::class.java).autowire() + val certificate = X509TestUtils.buildTestCertificate() + // @formatter:off + this.client!!.mutateWith(x509(certificate)) + .get() + .uri("/") + .exchange() + .expectStatus().isOk() + // @formatter:on + } + + private class SslInfoOverrideWebFilter(private val sslInfo: SslInfo) : WebFilter { + override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { + val sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo) + .build() + val sslInfoExchange = exchange.mutate().request(sslInfoRequest).build() + return chain.filter(sslInfoExchange) + } + } + + private fun loadCert(location: String): T { + try { + ClassPathResource(location).getInputStream().use { `is` -> + val certFactory = CertificateFactory.getInstance("X.509") + return certFactory.generateCertificate(`is`) as T + } + } catch (ex: Exception) { + throw IllegalArgumentException(ex) + } + } + + companion object { + private fun x509(certificate: X509Certificate): WebTestClientConfigurer { + return WebTestClientConfigurer { builder: WebTestClient.Builder, httpHandlerBuilder: WebHttpHandlerBuilder, connector: ClientHttpConnector? -> + + val sslInfo: SslInfo = object : SslInfo { + override fun getSessionId(): String { + return "sessionId" + } + + override fun getPeerCertificates(): Array { + return arrayOf(certificate) + } + } + httpHandlerBuilder.filters(Consumer { filters: MutableList -> + filters.add( + 0, + SslInfoOverrideWebFilter(sslInfo) + ) + }) + } + } + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/CustomX509Configuration.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/CustomX509Configuration.kt new file mode 100644 index 0000000000..b079ce26fb --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/CustomX509Configuration.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.kt.docs.servlet.authentication.servlet509config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.annotation.web.invoke +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.provisioning.InMemoryUserDetailsManager +import org.springframework.security.web.DefaultSecurityFilterChain +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor +import org.springframework.web.servlet.config.annotation.EnableWebMvc + +/** + * Demonstrates custom configuration for x509 reactive configuration. + * + * @author Rob Winch + */ +@EnableWebMvc +@EnableWebSecurity +@Configuration(proxyBeanMethods = false) +class CustomX509Configuration { + // tag::springSecurity[] + @Bean + fun springSecurity(http: HttpSecurity): DefaultSecurityFilterChain? { + val principalExtractor = SubjectX500PrincipalExtractor() + principalExtractor.setExtractPrincipalNameFromEmail(true) + + // @formatter:off + http { + authorizeHttpRequests { + authorize(anyRequest, authenticated) + } + x509 { + x509PrincipalExtractor = principalExtractor + } + } + return http.build() + } + // end::springSecurity[] + + @Bean + fun userDetailsService(): UserDetailsService { + // @formatter:off + val user = User + .withUsername("luke@monkeymachine") + .password("password") + .roles("USER") + .build() + // @formatter:on + return InMemoryUserDetailsManager(user) + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/DefaultX509Configuration.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/DefaultX509Configuration.kt new file mode 100644 index 0000000000..3c777ebe32 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/DefaultX509Configuration.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.kt.docs.servlet.authentication.servlet509config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.annotation.web.invoke +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.provisioning.InMemoryUserDetailsManager +import org.springframework.security.web.DefaultSecurityFilterChain +import org.springframework.web.servlet.config.annotation.EnableWebMvc + +/** + * Demonstrates custom configuration for x509 reactive configuration. + * + * @author Rob Winch + */ +@EnableWebMvc +@EnableWebSecurity +@Configuration(proxyBeanMethods = false) +class DefaultX509Configuration { + // tag::springSecurity[] + @Bean + fun springSecurity(http: HttpSecurity): DefaultSecurityFilterChain? { + // @formatter:off + http { + authorizeHttpRequests { + authorize(anyRequest, authenticated) + } + x509 { } + } + // @formatter:on + return http.build() + } + // end::springSecurity[] + + @Bean + fun userDetailsService(): UserDetailsService { + // @formatter:off + val user = User + .withUsername("rod") + .password("password") + .roles("USER") + .build() + // @formatter:on + return InMemoryUserDetailsManager(user) + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/X509ConfigurationTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/X509ConfigurationTests.kt new file mode 100644 index 0000000000..edaf503039 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/X509ConfigurationTests.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.kt.docs.servlet.authentication.servlet509config + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.security.config.test.SpringTestContext +import org.springframework.security.config.test.SpringTestContextExtension +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors +import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated +import org.springframework.security.web.authentication.preauth.x509.X509TestUtils +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +/** + * Tests [CustomX509Configuration]. + * + * @author Rob Winch + */ +@ExtendWith(SpringTestContextExtension::class) +class X509ConfigurationTests { + @JvmField + val spring: SpringTestContext = SpringTestContext(this) + + @Autowired + var mockMvc: MockMvc? = null + + @Test + @Throws(Exception::class) + fun x509WhenDefaultX509Configuration() { + this.spring.register(DefaultX509Configuration::class.java, Http200Controller::class.java).autowire() + // @formatter:off + this.mockMvc!!.perform(get("/").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer"))) + .andExpect(status().isOk()) + .andExpect(authenticated().withUsername("rod")) + // @formatter:on + } + + @Test + @Throws(Exception::class) + fun x509WhenCustomX509Configuration() { + this.spring.register(CustomX509Configuration::class.java, Http200Controller::class.java).autowire() + val certificate = X509TestUtils.buildTestCertificate() + // @formatter:off + this.mockMvc!!.perform(get("/").with(SecurityMockMvcRequestPostProcessors.x509(certificate))) + .andExpect(status().isOk()) + .andExpect(authenticated().withUsername("luke@monkeymachine")) + // @formatter:on + } + + @RestController + internal class Http200Controller { + @GetMapping("/**") + fun ok(): String { + return "ok" + } + } +} diff --git a/docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.xml b/docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.xml new file mode 100644 index 0000000000..cc2772e34b --- /dev/null +++ b/docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.xml b/docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.xml new file mode 100644 index 0000000000..62621d1cab --- /dev/null +++ b/docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + From b0cecb37d20468b7b9f01176951580138354c0aa Mon Sep 17 00:00:00 2001 From: Evgeniy Cheban Date: Fri, 18 Apr 2025 13:11:17 +0300 Subject: [PATCH 114/149] Replace deprecated #check calls with #authorize Closes gh-16936 Signed-off-by: Evgeniy Cheban --- .../AuthorizeHttpRequestsConfigurerTests.java | 6 ++++- .../authorization/AuthorizationManager.java | 8 +++---- .../ObservationAuthorizationManager.java | 23 +++++++++++++++---- .../ObservationAuthorizationManagerTests.java | 5 +++- ...MatcherDelegatingAuthorizationManager.java | 18 +++++++++++++-- ...MatcherDelegatingAuthorizationManager.java | 18 +++++++++++++-- 6 files changed, 63 insertions(+), 15 deletions(-) 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 057deea40c..7d57cae201 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,6 +85,7 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doCallRealMethod; @@ -153,6 +154,7 @@ public class AuthorizeHttpRequestsConfigurerTests { @Test public void configureMvcMatcherAccessAuthorizationManagerWhenNotNullThenVerifyUse() throws Exception { CustomAuthorizationManagerConfig.authorizationManager = mock(AuthorizationManager.class); + given(CustomAuthorizationManagerConfig.authorizationManager.authorize(any(), any())).willCallRealMethod(); this.spring.register(CustomAuthorizationManagerConfig.class, BasicController.class).autowire(); this.mvc.perform(get("/")).andExpect(status().isOk()); verify(CustomAuthorizationManagerConfig.authorizationManager).check(any(), any()); @@ -161,6 +163,8 @@ public class AuthorizeHttpRequestsConfigurerTests { @Test public void configureNoParameterMvcMatcherAccessAuthorizationManagerWhenNotNullThenVerifyUse() throws Exception { CustomAuthorizationManagerNoParameterConfig.authorizationManager = mock(AuthorizationManager.class); + given(CustomAuthorizationManagerNoParameterConfig.authorizationManager.authorize(any(), any())) + .willCallRealMethod(); this.spring.register(CustomAuthorizationManagerNoParameterConfig.class, BasicController.class).autowire(); this.mvc.perform(get("/")).andExpect(status().isOk()); verify(CustomAuthorizationManagerNoParameterConfig.authorizationManager).check(any(), any()); diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationManager.java index 758abce2c7..b039068b22 100644 --- a/core/src/main/java/org/springframework/security/authorization/AuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/AuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,9 +39,9 @@ public interface AuthorizationManager { * @throws AccessDeniedException if access is not granted */ default void verify(Supplier authentication, T object) { - AuthorizationDecision decision = check(authentication, object); - if (decision != null && !decision.isGranted()) { - throw new AuthorizationDeniedException("Access Denied", decision); + AuthorizationResult result = authorize(authentication, object); + if (result != null && !result.isGranted()) { + throw new AuthorizationDeniedException("Access Denied", result); } } 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 0494dbfecb..2c4a785f30 100644 --- a/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,6 +67,19 @@ public final class ObservationAuthorizationManager @Deprecated @Override public AuthorizationDecision check(Supplier authentication, T object) { + AuthorizationResult result = authorize(authentication, object); + if (result == null) { + return null; + } + if (result instanceof AuthorizationDecision decision) { + return decision; + } + throw new IllegalArgumentException( + "Please call #authorize or ensure that the returned result is of type AuthorizationDecision"); + } + + @Override + public AuthorizationResult authorize(Supplier authentication, T object) { AuthorizationObservationContext context = new AuthorizationObservationContext<>(object); Supplier wrapped = () -> { context.setAuthentication(authentication.get()); @@ -74,13 +87,13 @@ public final class ObservationAuthorizationManager }; Observation observation = Observation.createNotStarted(this.convention, () -> context, this.registry).start(); try (Observation.Scope scope = observation.openScope()) { - AuthorizationDecision decision = this.delegate.check(wrapped, object); - context.setAuthorizationResult(decision); - if (decision != null && !decision.isGranted()) { + AuthorizationResult result = this.delegate.authorize(wrapped, object); + context.setAuthorizationResult(result); + if (result != null && !result.isGranted()) { observation.error(new AccessDeniedException( this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access Denied"))); } - return decision; + return result; } catch (Throwable ex) { observation.error(ex); diff --git a/core/src/test/java/org/springframework/security/authorization/ObservationAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/ObservationAuthorizationManagerTests.java index 1921f7dc1a..bb2ccd8faa 100644 --- a/core/src/test/java/org/springframework/security/authorization/ObservationAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/ObservationAuthorizationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,6 +74,7 @@ public class ObservationAuthorizationManagerTests { void verifyWhenDefaultsThenObserves() { given(this.handler.supportsContext(any())).willReturn(true); given(this.authorizationManager.check(any(), any())).willReturn(this.grant); + given(this.authorizationManager.authorize(any(), any())).willCallRealMethod(); this.tested.verify(this.token, this.object); ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); verify(this.handler).onStart(captor.capture()); @@ -92,6 +93,7 @@ public class ObservationAuthorizationManagerTests { this.tested.setMessageSource(source); given(this.handler.supportsContext(any())).willReturn(true); given(this.authorizationManager.check(any(), any())).willReturn(this.deny); + given(this.authorizationManager.authorize(any(), any())).willCallRealMethod(); given(source.getMessage(eq("AbstractAccessDecisionManager.accessDenied"), any(), any(), any())) .willReturn("accessDenied"); assertThatExceptionOfType(AccessDeniedException.class) @@ -116,6 +118,7 @@ public class ObservationAuthorizationManagerTests { ((Supplier) invocation.getArgument(0)).get(); return this.grant; }); + given(this.authorizationManager.authorize(any(), any())).willCallRealMethod(); this.tested.verify(this.token, this.object); ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); verify(this.handler).onStart(captor.capture()); diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java index e7ff32718a..c24bb1b7b5 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java +++ b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java @@ -30,6 +30,7 @@ import org.springframework.security.authorization.AuthenticatedAuthorizationMana import org.springframework.security.authorization.AuthorityAuthorizationManager; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.SingleResultAuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.security.messaging.util.matcher.MessageMatcher; @@ -63,11 +64,24 @@ public final class MessageMatcherDelegatingAuthorizationManager implements Autho * @return an {@link AuthorizationDecision}. If there is no {@link MessageMatcher} * matching the message, or the {@link AuthorizationManager} could not decide, then * null is returned - * @deprecated please use {@link #authorize(Supplier, Object)} instead + * @deprecated please use {@link #authorize(Supplier, Message)} instead */ @Deprecated @Override public AuthorizationDecision check(Supplier authentication, Message message) { + AuthorizationResult result = authorize(authentication, message); + if (result == null) { + return null; + } + if (result instanceof AuthorizationDecision decision) { + return decision; + } + throw new IllegalArgumentException( + "Please call #authorize or ensure that the returned result is of type AuthorizationDecision"); + } + + @Override + public AuthorizationResult authorize(Supplier authentication, Message message) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Authorizing message")); } @@ -79,7 +93,7 @@ public final class MessageMatcherDelegatingAuthorizationManager implements Autho if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Checking authorization on message using %s", manager)); } - return manager.check(authentication, authorizationContext); + return manager.authorize(authentication, authorizationContext); } } this.logger.trace("Abstaining since did not find matching MessageMatcher"); diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java b/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java index b01ab43bbd..de660a3f97 100644 --- a/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java +++ b/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java @@ -30,6 +30,7 @@ import org.springframework.security.authorization.AuthenticatedAuthorizationMana import org.springframework.security.authorization.AuthorityAuthorizationManager; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.SingleResultAuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.security.web.util.UrlUtils; @@ -69,11 +70,24 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho * @return an {@link AuthorizationDecision}. If there is no {@link RequestMatcher} * matching the request, or the {@link AuthorizationManager} could not decide, then * null is returned - * @deprecated please use {@link #authorize(Supplier, Object)} instead + * @deprecated please use {@link #authorize(Supplier, HttpServletRequest)} instead */ @Deprecated @Override public AuthorizationDecision check(Supplier authentication, HttpServletRequest request) { + AuthorizationResult result = authorize(authentication, request); + if (result == null) { + return null; + } + if (result instanceof AuthorizationDecision decision) { + return decision; + } + throw new IllegalArgumentException( + "Please call #authorize or ensure that the returned result is of type AuthorizationDecision"); + } + + @Override + public AuthorizationResult authorize(Supplier authentication, HttpServletRequest request) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Authorizing %s", requestLine(request))); } @@ -87,7 +101,7 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho this.logger.trace( LogMessage.format("Checking authorization on %s using %s", requestLine(request), manager)); } - return manager.check(authentication, + return manager.authorize(authentication, new RequestAuthorizationContext(request, matchResult.getVariables())); } } From 092bbfc8e7e8d507e50908044b10e65d6a156472 Mon Sep 17 00:00:00 2001 From: Evgeniy Cheban Date: Thu, 24 Apr 2025 23:54:37 +0300 Subject: [PATCH 115/149] ReactiveAuthorizationManager replace deprecated #check calls with #authorize Closes gh-16936 Signed-off-by: Evgeniy Cheban --- ...servationReactiveAuthorizationManager.java | 19 +++++++++++++---- .../ReactiveAuthorizationManager.java | 6 +++--- ...tionReactiveAuthorizationManagerTests.java | 5 ++++- ...geMatcherReactiveAuthorizationManager.java | 18 +++++++++++++--- ...cherReactiveAuthorizationManagerTests.java | 6 +++++- ...elegatingReactiveAuthorizationManager.java | 21 +++++++++++++++---- ...tingReactiveAuthorizationManagerTests.java | 4 +++- 7 files changed, 62 insertions(+), 17 deletions(-) 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 a9d77c641b..0ef9b3a4be 100644 --- a/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,6 +62,17 @@ public final class ObservationReactiveAuthorizationManager @Deprecated @Override public Mono check(Mono authentication, T object) { + return authorize(authentication, object).flatMap((result) -> { + if (result instanceof AuthorizationDecision decision) { + return Mono.just(decision); + } + return Mono.error(new IllegalArgumentException( + "Please call #authorize or ensure that the returned result is of type Mono")); + }); + } + + @Override + public Mono authorize(Mono authentication, T object) { AuthorizationObservationContext context = new AuthorizationObservationContext<>(object); Mono wrapped = authentication.map((auth) -> { context.setAuthentication(auth); @@ -71,9 +82,9 @@ public final class ObservationReactiveAuthorizationManager Observation observation = Observation.createNotStarted(this.convention, () -> context, this.registry) .parentObservation(contextView.getOrDefault(ObservationThreadLocalAccessor.KEY, null)) .start(); - return this.delegate.check(wrapped, object).doOnSuccess((decision) -> { - context.setAuthorizationResult(decision); - if (decision == null || !decision.isGranted()) { + return this.delegate.authorize(wrapped, object).doOnSuccess((result) -> { + context.setAuthorizationResult(result); + if (result == null || !result.isGranted()) { observation.error(new AccessDeniedException("Access Denied")); } observation.stop(); diff --git a/core/src/main/java/org/springframework/security/authorization/ReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/ReactiveAuthorizationManager.java index 05662737d1..860208c6f5 100644 --- a/core/src/main/java/org/springframework/security/authorization/ReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/ReactiveAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,8 +50,8 @@ public interface ReactiveAuthorizationManager { */ default Mono verify(Mono authentication, T object) { // @formatter:off - return check(authentication, object) - .filter(AuthorizationDecision::isGranted) + return authorize(authentication, object) + .filter(AuthorizationResult::isGranted) .switchIfEmpty(Mono.defer(() -> Mono.error(new AccessDeniedException("Access Denied")))) .flatMap((decision) -> Mono.empty()); // @formatter:on diff --git a/core/src/test/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManagerTests.java index 34b0533a26..d3eb935cc7 100644 --- a/core/src/test/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,6 +70,7 @@ public class ObservationReactiveAuthorizationManagerTests { void verifyWhenDefaultsThenObserves() { given(this.handler.supportsContext(any())).willReturn(true); given(this.authorizationManager.check(any(), any())).willReturn(Mono.just(this.grant)); + given(this.authorizationManager.authorize(any(), any())).willCallRealMethod(); this.tested.verify(this.token, this.object).block(); ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); verify(this.handler).onStart(captor.capture()); @@ -86,6 +87,7 @@ public class ObservationReactiveAuthorizationManagerTests { void verifyWhenErrorsThenObserves() { given(this.handler.supportsContext(any())).willReturn(true); given(this.authorizationManager.check(any(), any())).willReturn(Mono.just(this.deny)); + given(this.authorizationManager.authorize(any(), any())).willCallRealMethod(); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.tested.verify(this.token, this.object).block()); ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); @@ -106,6 +108,7 @@ public class ObservationReactiveAuthorizationManagerTests { ((Mono) invocation.getArgument(0)).block(); return Mono.just(this.grant); }); + given(this.authorizationManager.authorize(any(), any())).willCallRealMethod(); this.tested.verify(this.token, this.object).block(); ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); verify(this.handler).onStart(captor.capture()); diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/authorization/PayloadExchangeMatcherReactiveAuthorizationManager.java b/rsocket/src/main/java/org/springframework/security/rsocket/authorization/PayloadExchangeMatcherReactiveAuthorizationManager.java index fc7862f51e..9004633f9e 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/authorization/PayloadExchangeMatcherReactiveAuthorizationManager.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/authorization/PayloadExchangeMatcherReactiveAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; 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.security.rsocket.api.PayloadExchange; @@ -51,18 +52,29 @@ public final class PayloadExchangeMatcherReactiveAuthorizationManager } /** - * @deprecated please use {@link #authorize(Mono, Object)} instead + * @deprecated please use {@link #authorize(Mono, PayloadExchange)} instead */ @Deprecated @Override public Mono check(Mono authentication, PayloadExchange exchange) { + return authorize(authentication, exchange).flatMap((result) -> { + if (result instanceof AuthorizationDecision decision) { + return Mono.just(decision); + } + return Mono.error(new IllegalArgumentException( + "Please call #authorize or ensure that the returned result is of type Mono")); + }); + } + + @Override + public Mono authorize(Mono authentication, PayloadExchange exchange) { return Flux.fromIterable(this.mappings) .concatMap((mapping) -> mapping.getMatcher() .matches(exchange) .filter(PayloadExchangeMatcher.MatchResult::isMatch) .map(MatchResult::getVariables) .flatMap((variables) -> mapping.getEntry() - .check(authentication, new PayloadExchangeAuthorizationContext(exchange, variables)))) + .authorize(authentication, new PayloadExchangeAuthorizationContext(exchange, variables)))) .next() .switchIfEmpty(Mono.fromCallable(() -> new AuthorizationDecision(false))); } diff --git a/rsocket/src/test/java/org/springframework/security/rsocket/authorization/PayloadExchangeMatcherReactiveAuthorizationManagerTests.java b/rsocket/src/test/java/org/springframework/security/rsocket/authorization/PayloadExchangeMatcherReactiveAuthorizationManagerTests.java index 4f8041aeb4..c0b14cb835 100644 --- a/rsocket/src/test/java/org/springframework/security/rsocket/authorization/PayloadExchangeMatcherReactiveAuthorizationManagerTests.java +++ b/rsocket/src/test/java/org/springframework/security/rsocket/authorization/PayloadExchangeMatcherReactiveAuthorizationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,6 +53,7 @@ public class PayloadExchangeMatcherReactiveAuthorizationManagerTests { public void checkWhenGrantedThenGranted() { AuthorizationDecision expected = new AuthorizationDecision(true); given(this.authz.check(any(), any())).willReturn(Mono.just(expected)); + given(this.authz.authorize(any(), any())).willCallRealMethod(); PayloadExchangeMatcherReactiveAuthorizationManager manager = PayloadExchangeMatcherReactiveAuthorizationManager .builder() .add(new PayloadExchangeMatcherEntry<>(PayloadExchangeMatchers.anyExchange(), this.authz)) @@ -64,6 +65,7 @@ public class PayloadExchangeMatcherReactiveAuthorizationManagerTests { public void checkWhenDeniedThenDenied() { AuthorizationDecision expected = new AuthorizationDecision(false); given(this.authz.check(any(), any())).willReturn(Mono.just(expected)); + given(this.authz.authorize(any(), any())).willCallRealMethod(); PayloadExchangeMatcherReactiveAuthorizationManager manager = PayloadExchangeMatcherReactiveAuthorizationManager .builder() .add(new PayloadExchangeMatcherEntry<>(PayloadExchangeMatchers.anyExchange(), this.authz)) @@ -75,6 +77,7 @@ public class PayloadExchangeMatcherReactiveAuthorizationManagerTests { public void checkWhenFirstMatchThenSecondUsed() { AuthorizationDecision expected = new AuthorizationDecision(true); given(this.authz.check(any(), any())).willReturn(Mono.just(expected)); + given(this.authz.authorize(any(), any())).willCallRealMethod(); PayloadExchangeMatcherReactiveAuthorizationManager manager = PayloadExchangeMatcherReactiveAuthorizationManager .builder() .add(new PayloadExchangeMatcherEntry<>(PayloadExchangeMatchers.anyExchange(), this.authz)) @@ -87,6 +90,7 @@ public class PayloadExchangeMatcherReactiveAuthorizationManagerTests { public void checkWhenSecondMatchThenSecondUsed() { AuthorizationDecision expected = new AuthorizationDecision(true); given(this.authz2.check(any(), any())).willReturn(Mono.just(expected)); + given(this.authz2.authorize(any(), any())).willCallRealMethod(); PayloadExchangeMatcherReactiveAuthorizationManager manager = PayloadExchangeMatcherReactiveAuthorizationManager .builder() .add(new PayloadExchangeMatcherEntry<>((e) -> PayloadExchangeMatcher.MatchResult.notMatch(), this.authz)) diff --git a/web/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java b/web/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java index ebdb2b8ccb..e245ce0466 100644 --- a/web/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java +++ b/web/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import reactor.core.publisher.Mono; import org.springframework.core.log.LogMessage; 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.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult; @@ -35,6 +36,7 @@ import org.springframework.web.server.ServerWebExchange; /** * @author Rob Winch * @author Mathieu Ouellet + * @author Evgeniy Cheban * @since 5.0 */ public final class DelegatingReactiveAuthorizationManager implements ReactiveAuthorizationManager { @@ -49,11 +51,22 @@ public final class DelegatingReactiveAuthorizationManager implements ReactiveAut } /** - * @deprecated please use {@link #authorize(Mono, Object)} instead + * @deprecated please use {@link #authorize(Mono, ServerWebExchange)} instead */ @Deprecated @Override public Mono check(Mono authentication, ServerWebExchange exchange) { + return authorize(authentication, exchange).flatMap((result) -> { + if (result instanceof AuthorizationDecision decision) { + return Mono.just(decision); + } + return Mono.error(new IllegalArgumentException( + "Please call #authorize or ensure that the returned result is of type Mono")); + }); + } + + @Override + public Mono authorize(Mono authentication, ServerWebExchange exchange) { return Flux.fromIterable(this.mappings) .concatMap((mapping) -> mapping.getMatcher() .matches(exchange) @@ -63,10 +76,10 @@ public final class DelegatingReactiveAuthorizationManager implements ReactiveAut logger.debug(LogMessage.of(() -> "Checking authorization on '" + exchange.getRequest().getPath().pathWithinApplication() + "' using " + mapping.getEntry())); - return mapping.getEntry().check(authentication, new AuthorizationContext(exchange, variables)); + return mapping.getEntry().authorize(authentication, new AuthorizationContext(exchange, variables)); })) .next() - .defaultIfEmpty(new AuthorizationDecision(false)); + .switchIfEmpty(Mono.fromCallable(() -> new AuthorizationDecision(false))); } public static DelegatingReactiveAuthorizationManager.Builder builder() { diff --git a/web/src/test/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManagerTests.java b/web/src/test/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManagerTests.java index 8d916142d2..adab9bb717 100644 --- a/web/src/test/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManagerTests.java +++ b/web/src/test/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,6 +81,7 @@ public class DelegatingReactiveAuthorizationManagerTests { given(this.match1.matches(any())).willReturn(ServerWebExchangeMatcher.MatchResult.match()); given(this.delegate1.check(eq(this.authentication), any(AuthorizationContext.class))) .willReturn(Mono.just(this.decision)); + given(this.delegate1.authorize(eq(this.authentication), any(AuthorizationContext.class))).willCallRealMethod(); assertThat(this.manager.check(this.authentication, this.exchange).block()).isEqualTo(this.decision); verifyNoMoreInteractions(this.match2, this.delegate2); } @@ -91,6 +92,7 @@ public class DelegatingReactiveAuthorizationManagerTests { given(this.match2.matches(any())).willReturn(ServerWebExchangeMatcher.MatchResult.match()); given(this.delegate2.check(eq(this.authentication), any(AuthorizationContext.class))) .willReturn(Mono.just(this.decision)); + given(this.delegate2.authorize(eq(this.authentication), any(AuthorizationContext.class))).willCallRealMethod(); assertThat(this.manager.check(this.authentication, this.exchange).block()).isEqualTo(this.decision); verifyNoMoreInteractions(this.delegate1); } From 040ffe17e590bcd3c08032b33424dd3aea513f1d Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:19:37 -0500 Subject: [PATCH 116/149] Add SubjectX500PrincipalExtractor to Whats New Issue gh-16984 --- docs/modules/ROOT/pages/whats-new.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index 1f86df6989..79f7ba57ff 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -3,3 +3,7 @@ Spring Security 7.0 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. + +== Web + +* Added javadoc:org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor[] From 043464fd3d7ed5f397b6145f5bb26a89f900e40a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 03:47:51 +0000 Subject: [PATCH 117/149] Bump org.hibernate.orm:hibernate-core from 7.0.1.Final to 7.0.2.Final Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.0.1.Final to 7.0.2.Final. - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.0.2/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.0.1...7.0.2) --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 7.0.2.Final dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1969124ad4..6699e8e4dc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,7 +71,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", 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:7.0.1.Final" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.0.2.Final" org-hsqldb = "org.hsqldb:hsqldb:2.7.4" 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.25" From 5130cbb99f2c1a3ea0a3be06691e2767eb8c7809 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 03:48:15 +0000 Subject: [PATCH 118/149] Bump org.springframework.ldap:spring-ldap-core from 3.2.12 to 3.2.13 Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.12 to 3.2.13. - [Release notes](https://github.com/spring-projects/spring-ldap/releases) - [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt) - [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.12...3.2.13) --- updated-dependencies: - dependency-name: org.springframework.ldap:spring-ldap-core dependency-version: 3.2.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1969124ad4..ddf324f57b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -90,7 +90,7 @@ org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17" org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.0-SNAPSHOT" -org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.12" +org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.13" 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" From 1f57df6caa2ce5ff69723a95eb4634c8b5935e6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 03:37:16 +0000 Subject: [PATCH 119/149] Bump com.fasterxml.jackson:jackson-bom from 2.19.0 to 2.19.1 Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.19.0 to 2.19.1. - [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.19.0...jackson-bom-2.19.1) --- updated-dependencies: - dependency-name: com.fasterxml.jackson:jackson-bom dependency-version: 2.19.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1969124ad4..f8ffc67025 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ org-springframework = "7.0.0-SNAPSHOT" [libraries] ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.18" -com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.19.0" +com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.19.1" 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" From ae1537b409a9a4c2baf0b13ddec99fcad466bf49 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Jun 2025 15:08:20 +0000 Subject: [PATCH 120/149] Release 6.3.10 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 65a1331cd8..214b134b2b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ springBootVersion=3.1.1 -version=6.3.10-SNAPSHOT +version=6.3.10 samplesBranch=6.3.x org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true From 30056be789522cca994eb6a939d85736eb774696 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Jun 2025 15:08:24 +0000 Subject: [PATCH 121/149] Release 6.4.7 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 2d5886cf13..3a02d4a776 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ # limitations under the License. # springBootVersion=3.3.3 -version=6.4.7-SNAPSHOT +version=6.4.7 samplesBranch=main org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true From 449c7a8419d8806d843e8983d1c1a0a26a8eb6f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Jun 2025 15:34:45 +0000 Subject: [PATCH 122/149] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 214b134b2b..bae90d6377 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ springBootVersion=3.1.1 -version=6.3.10 +version=6.3.11-SNAPSHOT samplesBranch=6.3.x org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true From 726b9a80a0530a49e192e326f9bc760824223209 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Jun 2025 18:46:36 +0000 Subject: [PATCH 123/149] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3a02d4a776..a189ce8b53 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ # limitations under the License. # springBootVersion=3.3.3 -version=6.4.7 +version=6.4.8-SNAPSHOT samplesBranch=main org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true From 063ee71e0d21ba20eadaac77bc6c6a887b57a7e4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Jun 2025 18:47:03 +0000 Subject: [PATCH 124/149] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4be2d2838f..b8c26375bc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ # limitations under the License. # springBootVersion=3.3.3 -version=6.5.1 +version=6.5.2-SNAPSHOT samplesBranch=main org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true From 602aa1c46c72e4c18f585850936ed1e8f72f1750 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:30:27 +0000 Subject: [PATCH 125/149] Bump org.hibernate.orm:hibernate-core from 6.6.17.Final to 6.6.18.Final Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.17.Final to 6.6.18.Final. - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.18/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.17...6.6.18) --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 6.6.18.Final dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b6377c39c2..98c4e279d0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,7 +71,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", 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.6.17.Final" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.18.Final" org-hsqldb = "org.hsqldb:hsqldb:2.7.4" 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.25" From bd31a4082481421e657470fb6064ec30d43abd44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:32:10 +0000 Subject: [PATCH 126/149] Bump com.fasterxml.jackson:jackson-bom from 2.19.0 to 2.19.1 Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.19.0 to 2.19.1. - [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.19.0...jackson-bom-2.19.1) --- updated-dependencies: - dependency-name: com.fasterxml.jackson:jackson-bom dependency-version: 2.19.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1969124ad4..f8ffc67025 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ org-springframework = "7.0.0-SNAPSHOT" [libraries] ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.18" -com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.19.0" +com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.19.1" 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" From d871f7f295efac4dbf640c43a64edc50f7b48cfb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:33:42 +0000 Subject: [PATCH 127/149] Bump org.springframework.data:spring-data-bom Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.0.12 to 2024.0.13. - [Release notes](https://github.com/spring-projects/spring-data-bom/releases) - [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.0.12...2024.0.13) --- updated-dependencies: - dependency-name: org.springframework.data:spring-data-bom dependency-version: 2024.0.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 94b8454fc8..a661b77884 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -84,7 +84,7 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17" -org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.0.12" +org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.0.13" org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.12" 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" From 854375e3901d053745f794907bbeba69304d7762 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:37:19 +0000 Subject: [PATCH 128/149] Bump org.springframework.ldap:spring-ldap-core from 3.2.12 to 3.2.13 Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.12 to 3.2.13. - [Release notes](https://github.com/spring-projects/spring-ldap/releases) - [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt) - [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.12...3.2.13) --- updated-dependencies: - dependency-name: org.springframework.ldap:spring-ldap-core dependency-version: 3.2.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b6377c39c2..a716c7468a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -90,7 +90,7 @@ org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17" org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.6" -org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.12" +org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.13" 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" From f0c34eb39deeb3123f35cf67c3b42a8b5dbe0a94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:40:53 +0000 Subject: [PATCH 129/149] Bump org.springframework.ldap:spring-ldap-core from 3.2.12 to 3.2.13 Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.12 to 3.2.13. - [Release notes](https://github.com/spring-projects/spring-ldap/releases) - [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt) - [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.12...3.2.13) --- updated-dependencies: - dependency-name: org.springframework.ldap:spring-ldap-core dependency-version: 3.2.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 94b8454fc8..d69c3f634c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -85,7 +85,7 @@ org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17" org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.0.12" -org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.12" +org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.13" 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" From 9f1ba97bb09bfb8e9c5f2a537ea371fac2c224bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:43:53 +0000 Subject: [PATCH 130/149] Bump org.springframework:spring-framework-bom from 6.1.20 to 6.1.21 Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.20 to 6.1.21. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.20...v6.1.21) --- updated-dependencies: - dependency-name: org.springframework:spring-framework-bom dependency-version: 6.1.21 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 94b8454fc8..36d70d0ff0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ org-jetbrains-kotlin = "1.9.25" org-jetbrains-kotlinx = "1.8.1" org-mockito = "5.11.0" org-opensaml = "4.3.2" -org-springframework = "6.1.20" +org-springframework = "6.1.21" [libraries] ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.18" From c2ecc8b5387a62d0a712b00103c1a6ee425058fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:47:07 +0000 Subject: [PATCH 131/149] Bump org.springframework.ldap:spring-ldap-core from 3.2.12 to 3.2.13 Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.12 to 3.2.13. - [Release notes](https://github.com/spring-projects/spring-ldap/releases) - [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt) - [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.12...3.2.13) --- updated-dependencies: - dependency-name: org.springframework.ldap:spring-ldap-core dependency-version: 3.2.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1969124ad4..ddf324f57b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -90,7 +90,7 @@ org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17" org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.0-SNAPSHOT" -org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.12" +org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.13" 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" From d933f1b88195378e7be6731da43da333b071282c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:47:26 +0000 Subject: [PATCH 132/149] Bump org.springframework:spring-framework-bom from 6.2.7 to 6.2.8 Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.7 to 6.2.8. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.7...v6.2.8) --- updated-dependencies: - dependency-name: org.springframework:spring-framework-bom dependency-version: 6.2.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b6377c39c2..f38fb1f764 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ org-jetbrains-kotlinx = "1.10.2" org-mockito = "5.17.0" org-opensaml = "4.3.2" org-opensaml5 = "5.1.2" -org-springframework = "6.2.7" +org-springframework = "6.2.8" [libraries] ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.18" From fce40bc3ed5f2dafbe43272645db26341f1a789c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:48:38 +0000 Subject: [PATCH 133/149] Bump org.hibernate.orm:hibernate-core from 7.0.1.Final to 7.0.2.Final Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.0.1.Final to 7.0.2.Final. - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.0.2/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.0.1...7.0.2) --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 7.0.2.Final dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1969124ad4..6699e8e4dc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,7 +71,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", 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:7.0.1.Final" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.0.2.Final" org-hsqldb = "org.hsqldb:hsqldb:2.7.4" 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.25" From f69aad2e547c7088ca33a5fe65f052ec95f4101e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:49:39 +0000 Subject: [PATCH 134/149] Bump org.springframework.data:spring-data-bom from 2024.1.6 to 2024.1.7 Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.1.6 to 2024.1.7. - [Release notes](https://github.com/spring-projects/spring-data-bom/releases) - [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.1.6...2024.1.7) --- updated-dependencies: - dependency-name: org.springframework.data:spring-data-bom dependency-version: 2024.1.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b6377c39c2..c98e12ecdf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -89,7 +89,7 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17" -org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.6" +org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.7" org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.12" 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" From bbc6fb3ac211037b9920a6063b4d68632a7aa9f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 04:01:36 +0000 Subject: [PATCH 135/149] Bump org.springframework:spring-framework-bom from 6.2.7 to 6.2.8 Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.7 to 6.2.8. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.7...v6.2.8) --- updated-dependencies: - dependency-name: org.springframework:spring-framework-bom dependency-version: 6.2.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8754f41e4f..787dd450ad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ org-jetbrains-kotlinx = "1.9.0" org-mockito = "5.14.2" org-opensaml = "4.3.2" org-opensaml5 = "5.1.2" -org-springframework = "6.2.7" +org-springframework = "6.2.8" [libraries] ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.18" From 605d497ef6e88786f7a51d7b7a0ca3bc2ae284a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 04:01:49 +0000 Subject: [PATCH 136/149] Bump org.springframework.data:spring-data-bom from 2024.1.6 to 2024.1.7 Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.1.6 to 2024.1.7. - [Release notes](https://github.com/spring-projects/spring-data-bom/releases) - [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.1.6...2024.1.7) --- updated-dependencies: - dependency-name: org.springframework.data:spring-data-bom dependency-version: 2024.1.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8754f41e4f..78d46328ee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -88,7 +88,7 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17" -org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.6" +org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.7" org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.12" 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" From cdd71d2bd58e9874b604938ab91f0acabfa7b036 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 04:02:08 +0000 Subject: [PATCH 137/149] Bump org.hibernate.orm:hibernate-core from 6.6.17.Final to 6.6.18.Final Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.17.Final to 6.6.18.Final. - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.18/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.17...6.6.18) --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 6.6.18.Final dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8754f41e4f..0475e149f3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -70,7 +70,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", 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.6.17.Final" +org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.18.Final" org-hsqldb = "org.hsqldb:hsqldb:2.7.4" 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.25" From d962701375ad360edd2f0dd3cddedb45d641b8b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 04:02:21 +0000 Subject: [PATCH 138/149] Bump org.springframework.ldap:spring-ldap-core from 3.2.12 to 3.2.13 Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.12 to 3.2.13. - [Release notes](https://github.com/spring-projects/spring-ldap/releases) - [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt) - [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.12...3.2.13) --- updated-dependencies: - dependency-name: org.springframework.ldap:spring-ldap-core dependency-version: 3.2.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8754f41e4f..4208ab834d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -89,7 +89,7 @@ org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17" org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.6" -org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.12" +org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.13" 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" From b2325e41764e0ea2ea04bcfd2c345c610edf8bf7 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Thu, 8 May 2025 16:21:09 -0500 Subject: [PATCH 139/149] Add OAuth Support for HTTP Interface Client Closes gh-16858 --- docs/modules/ROOT/nav.adoc | 2 + .../integrations/rest/http-interface.adoc | 66 +++++++++++ .../oauth2/client/authorized-clients.adoc | 5 + docs/modules/ROOT/pages/whats-new.adoc | 1 + docs/spring-security-docs.gradle | 3 + .../rest/clientregistrationid/User.java | 28 +++++ .../clientregistrationid/UserService.java | 36 ++++++ ...HttpInterfaceIntegrationConfiguration.java | 67 +++++++++++ ...nterfaceIntegrationConfigurationTests.java | 73 ++++++++++++ ...nterfaceIntegrationConfigurationTests.java | 75 ++++++++++++ ...HttpInterfaceIntegrationConfiguration.java | 69 +++++++++++ .../rest/clientregistrationid/User.kt | 29 +++++ .../rest/clientregistrationid/UserService.kt | 35 ++++++ ...ntHttpInterfaceIntegrationConfiguration.kt | 65 +++++++++++ ...pInterfaceIntegrationConfigurationTests.kt | 75 ++++++++++++ ...pInterfaceIntegrationConfigurationTests.kt | 78 +++++++++++++ ...ntHttpInterfaceIntegrationConfiguration.kt | 72 ++++++++++++ .../annotation/ClientRegistrationId.java | 58 ++++++++++ .../oauth2/client/web/ClientAttributes.java | 69 +++++++++++ .../client/ClientRegistrationIdProcessor.java | 51 +++++++++ ...AttributeClientRegistrationIdResolver.java | 11 +- ...2RestClientHttpServiceGroupConfigurer.java | 67 +++++++++++ ...uthorizedClientExchangeFilterFunction.java | 12 +- ...uthorizedClientExchangeFilterFunction.java | 8 +- ...h2WebClientHttpServiceGroupConfigurer.java | 92 +++++++++++++++ ...verClientRegistrationIdProcessorTests.java | 108 ++++++++++++++++++ ...egistrationIdProcessorRestClientTests.java | 60 ++++++++++ .../ClientRegistrationIdProcessorTests.java | 94 +++++++++++++++ ...RegistrationIdProcessorWebClientTests.java | 82 +++++++++++++ ...ClientHttpServiceGroupConfigurerTests.java | 89 +++++++++++++++ ...ClientHttpServiceGroupConfigurerTests.java | 89 +++++++++++++++ 31 files changed, 1647 insertions(+), 22 deletions(-) create mode 100644 docs/modules/ROOT/pages/features/integrations/rest/http-interface.adoc create mode 100644 docs/src/test/java/org/springframework/security/docs/features/integrations/rest/clientregistrationid/User.java create mode 100644 docs/src/test/java/org/springframework/security/docs/features/integrations/rest/clientregistrationid/UserService.java create mode 100644 docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfiguration.java create mode 100644 docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfigurationTests.java create mode 100644 docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationwebclient/ServerRestClientHttpInterfaceIntegrationConfigurationTests.java create mode 100644 docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationwebclient/ServerWebClientHttpInterfaceIntegrationConfiguration.java create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/clientregistrationid/User.kt create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/clientregistrationid/UserService.kt create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfiguration.kt create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfigurationTests.kt create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationwebclient/ServerRestClientHttpInterfaceIntegrationConfigurationTests.kt create mode 100644 docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationwebclient/ServerWebClientHttpInterfaceIntegrationConfiguration.kt create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/annotation/ClientRegistrationId.java create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/ClientAttributes.java create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessor.java create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/support/OAuth2RestClientHttpServiceGroupConfigurer.java create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/support/OAuth2WebClientHttpServiceGroupConfigurer.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/AbstractMockServerClientRegistrationIdProcessorTests.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorRestClientTests.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorTests.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorWebClientTests.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/support/OAuth2RestClientHttpServiceGroupConfigurerTests.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/support/OAuth2WebClientHttpServiceGroupConfigurerTests.java diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 24ca536e13..c113bbc609 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -19,6 +19,8 @@ *** xref:features/exploits/headers.adoc[HTTP Headers] *** xref:features/exploits/http.adoc[HTTP Requests] ** xref:features/integrations/index.adoc[Integrations] +*** REST Client +**** xref:features/integrations/rest/http-interface.adoc[HTTP Interface Integration] *** xref:features/integrations/cryptography.adoc[Cryptography] *** xref:features/integrations/data.adoc[Spring Data] *** xref:features/integrations/concurrency.adoc[Java's Concurrency APIs] diff --git a/docs/modules/ROOT/pages/features/integrations/rest/http-interface.adoc b/docs/modules/ROOT/pages/features/integrations/rest/http-interface.adoc new file mode 100644 index 0000000000..535ae27bdb --- /dev/null +++ b/docs/modules/ROOT/pages/features/integrations/rest/http-interface.adoc @@ -0,0 +1,66 @@ += HTTP Interface Integration + +Spring Security's OAuth Support can integrate with `RestClient` and `WebClient` {spring-framework-reference-url}/integration/rest-clients.html[HTTP Interface based REST Clients]. + + +[[configuration]] +== Configuration +After xref:features/integrations/rest/http-interface.adoc#configuration-restclient[RestClient] or xref:features/integrations/rest/http-interface.adoc#configuration-webclient[WebClient] specific configuration, usage of xref:features/integrations/rest/http-interface.adoc[] only requires adding a xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`] to methods that require OAuth. + +Since the presense of xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`] determines if and how the OAuth token will be resolved, it is safe to add Spring Security's OAuth support any configuration. + +[[configuration-restclient]] +=== RestClient Configuration + +Spring Security's OAuth Support can integrate with {spring-framework-reference-url}/integration/rest-clients.html[HTTP Interface based REST Clients] backed by RestClient. +The first step is to xref:servlet/oauth2/client/core.adoc#oauth2Client-authorized-manager-provider[create an `OAuthAuthorizedClientManager` Bean]. + +Next you must configure `HttpServiceProxyFactory` and `RestClient` to be aware of xref:./http-interface.adoc#client-registration-id[@ClientRegistrationId] +To simplify this configuration, use javadoc:org.springframework.security.oauth2.client.web.client.support.OAuth2RestClientHttpServiceGroupConfigurer[]. + +include-code::./RestClientHttpInterfaceIntegrationConfiguration[tag=config,indent=0] + +The configuration: + +- Adds xref:features/integrations/rest/http-interface.adoc#client-registration-id-processor[`ClientRegistrationIdProcessor`] to {spring-framework-reference-url}/integration/rest-clients.html#rest-http-interface[`HttpServiceProxyFactory`] +- Adds xref:servlet/oauth2/client/authorized-clients.adoc#oauth2-client-rest-client[`OAuth2ClientHttpRequestInterceptor`] to the `RestClient` + +[[configuration-webclient]] +=== WebClient Configuration + +Spring Security's OAuth Support can integrate with {spring-framework-reference-url}/integration/rest-clients.html[HTTP Interface based REST Clients] backed by `WebClient`. +The first step is to xref:reactive/oauth2/client/core.adoc#oauth2Client-authorized-manager-provider[create an `ReactiveOAuthAuthorizedClientManager` Bean]. + +Next you must configure `HttpServiceProxyFactory` and `WebRestClient` to be aware of xref:./http-interface.adoc#client-registration-id[@ClientRegistrationId] +To simplify this configuration, use javadoc:org.springframework.security.oauth2.client.web.reactive.function.client.support.OAuth2WebClientHttpServiceGroupConfigurer[]. + +include-code::./ServerWebClientHttpInterfaceIntegrationConfiguration[tag=config,indent=0] + +The configuration: + +- Adds xref:features/integrations/rest/http-interface.adoc#client-registration-id-processor[`ClientRegistrationIdProcessor`] to {spring-framework-reference-url}/integration/rest-clients.html#rest-http-interface[`HttpServiceProxyFactory`] +- Adds xref:reactive/oauth2/client/authorized-clients.adoc#oauth2-client-web-client[`ServerOAuth2AuthorizedClientExchangeFilterFunction`] to the `WebClient` + + +[[client-registration-id]] +== @ClientRegistrationId + +You can add the javadoc:org.springframework.security.oauth2.client.annotation.ClientRegistrationId[] on the HTTP Interface to specify which javadoc:org.springframework.security.oauth2.client.registration.ClientRegistration[] to use. + +include-code::./UserService[tag=getAuthenticatedUser] + +The xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`] will be processed by xref:features/integrations/rest/http-interface.adoc#client-registration-id-processor[`ClientRegistrationIdProcessor`] + +[[client-registration-id-processor]] +== `ClientRegistrationIdProcessor` + +The xref:features/integrations/rest/http-interface.adoc#configuration[configured] javadoc:org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor[] will: + +- Automatically invoke javadoc:org.springframework.security.oauth2.client.web.ClientAttributes#clientRegistrationId(java.lang.String)[] for each xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`]. +- This adds the javadoc:org.springframework.security.oauth2.client.registration.ClientRegistration#getId()[] to the attributes + +The `id` is then processed by: + +- `OAuth2ClientHttpRequestInterceptor` for xref:servlet/oauth2/client/authorized-clients.adoc#oauth2-client-rest-client[RestClient Integration] +- xref:servlet/oauth2/client/authorized-clients.adoc#oauth2-client-web-client[`ServletOAuth2AuthorizedClientExchangeFilterFunction`] (servlets) or xref:servlet/oauth2/client/authorized-clients.adoc#oauth2-client-web-client[`ServerOAuth2AuthorizedClientExchangeFilterFunction`] (reactive environments) for `WebClient`. + diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/authorized-clients.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/authorized-clients.adoc index 8864e4e7db..aa721f1413 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/authorized-clients.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/authorized-clients.adoc @@ -495,6 +495,11 @@ class RestClientConfig { ---- ===== +[[oauth2-client-rest-client-interface]] +=== HTTP Interface Integration + +Spring Security's OAuth support integrates with xref:features/integrations/rest/http-interface.adoc[]. + [[oauth2-client-web-client]] == [[oauth2Client-webclient-servlet]]WebClient Integration for Servlet Environments diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index 79f7ba57ff..140efdf0d4 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -7,3 +7,4 @@ Below are the highlights of the release, or you can view https://github.com/spri == Web * Added javadoc:org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor[] +* Added OAuth2 Support for xref:features/integrations/rest/http-interface.adoc[HTTP Interface Integration] diff --git a/docs/spring-security-docs.gradle b/docs/spring-security-docs.gradle index ec459fc115..680ab725e9 100644 --- a/docs/spring-security-docs.gradle +++ b/docs/spring-security-docs.gradle @@ -39,6 +39,8 @@ dependencies { testImplementation project(':spring-security-config') testImplementation project(path : ':spring-security-config', configuration : 'tests') testImplementation project(':spring-security-test') + testImplementation project(':spring-security-oauth2-client') + testImplementation 'com.squareup.okhttp3:mockwebserver' testImplementation 'com.unboundid:unboundid-ldapsdk' testImplementation libs.webauthn4j.core testImplementation 'org.jetbrains.kotlin:kotlin-reflect' @@ -49,6 +51,7 @@ dependencies { testImplementation 'org.springframework:spring-webmvc' testImplementation 'jakarta.servlet:jakarta.servlet-api' + testImplementation 'io.mockk:mockk' testImplementation "org.junit.jupiter:junit-jupiter-api" testImplementation "org.junit.jupiter:junit-jupiter-params" testImplementation "org.junit.jupiter:junit-jupiter-engine" diff --git a/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/clientregistrationid/User.java b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/clientregistrationid/User.java new file mode 100644 index 0000000000..08efa3bfef --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/clientregistrationid/User.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.features.integrations.rest.clientregistrationid; + +/** + * A user. + * @param login + * @param id + * @param name + * @author Rob Winch + * @see UserService + */ +public record User(String login, int id, String name) { +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/clientregistrationid/UserService.java b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/clientregistrationid/UserService.java new file mode 100644 index 0000000000..f0088f887b --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/clientregistrationid/UserService.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.features.integrations.rest.clientregistrationid; + +import org.springframework.security.oauth2.client.annotation.ClientRegistrationId; +import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.annotation.HttpExchange; + +/** + * Demonstrates a service for {@link ClientRegistrationId} and HTTP Interface clients. + * @author Rob Winch + */ +@HttpExchange +public interface UserService { + + // tag::getAuthenticatedUser[] + @GetExchange("/user") + @ClientRegistrationId("github") + User getAuthenticatedUser(); + // end::getAuthenticatedUser[] + +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfiguration.java b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfiguration.java new file mode 100644 index 0000000000..73d8b37ff0 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.features.integrations.rest.configurationrestclient; + +import okhttp3.mockwebserver.MockWebServer; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.docs.features.integrations.rest.clientregistrationid.UserService; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.client.support.OAuth2RestClientHttpServiceGroupConfigurer; +import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer; +import org.springframework.web.service.registry.ImportHttpServices; + +import static org.mockito.Mockito.mock; + +/** + * Documentation for {@link OAuth2RestClientHttpServiceGroupConfigurer}. + * @author Rob Winch + */ +@Configuration(proxyBeanMethods = false) +@ImportHttpServices(types = UserService.class) +public class RestClientHttpInterfaceIntegrationConfiguration { + + // tag::config[] + @Bean + OAuth2RestClientHttpServiceGroupConfigurer securityConfigurer( + OAuth2AuthorizedClientManager manager) { + return OAuth2RestClientHttpServiceGroupConfigurer.from(manager); + } + // end::config[] + + @Bean + OAuth2AuthorizedClientManager authorizedClientManager() { + return mock(OAuth2AuthorizedClientManager.class); + } + + @Bean + RestClientHttpServiceGroupConfigurer groupConfigurer(MockWebServer server) { + return groups -> { + + groups + .forEachClient((group, builder) -> builder + .baseUrl(server.url("").toString()) + .defaultHeader("Accept", "application/vnd.github.v3+json")); + }; + } + + @Bean + MockWebServer mockServer() { + return new MockWebServer(); + } +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfigurationTests.java b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfigurationTests.java new file mode 100644 index 0000000000..5823aa90da --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfigurationTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.features.integrations.rest.configurationrestclient; + +import java.time.Duration; +import java.time.Instant; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; +import org.springframework.security.docs.features.integrations.rest.clientregistrationid.UserService; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +/** + * Tests RestClient configuration for HTTP Interface clients. + * @author Rob Winch + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = RestClientHttpInterfaceIntegrationConfiguration.class) +class RestClientHttpInterfaceIntegrationConfigurationTests { + + @Test + void getAuthenticatedUser(@Autowired MockWebServer webServer, @Autowired OAuth2AuthorizedClientManager authorizedClients, @Autowired UserService users) + throws InterruptedException { + ClientRegistration registration = CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("github").build(); + + Instant issuedAt = Instant.now(); + Instant expiresAt = issuedAt.plus(Duration.ofMinutes(5)); + OAuth2AccessToken token = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "1234", + issuedAt, expiresAt); + OAuth2AuthorizedClient result = new OAuth2AuthorizedClient(registration, "rob", token); + given(authorizedClients.authorize(any())).willReturn(result); + + webServer.enqueue(new MockResponse().addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody( + """ + {"login": "rob_winch", "id": 1234, "name": "Rob Winch" } + """)); + + users.getAuthenticatedUser(); + + assertThat(webServer.takeRequest().getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + token.getTokenValue()); + } + +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationwebclient/ServerRestClientHttpInterfaceIntegrationConfigurationTests.java b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationwebclient/ServerRestClientHttpInterfaceIntegrationConfigurationTests.java new file mode 100644 index 0000000000..bdecbdc9b9 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationwebclient/ServerRestClientHttpInterfaceIntegrationConfigurationTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.features.integrations.rest.configurationwebclient; + +import java.time.Duration; +import java.time.Instant; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +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.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; +import org.springframework.security.docs.features.integrations.rest.clientregistrationid.UserService; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +/** + * Demonstrates configuring RestClient with interface based proxy clients. + * @author Rob Winch + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = ServerWebClientHttpInterfaceIntegrationConfiguration.class) +class ServerRestClientHttpInterfaceIntegrationConfigurationTests { + + @Test + void getAuthenticatedUser(@Autowired MockWebServer webServer, @Autowired ReactiveOAuth2AuthorizedClientManager authorizedClients, @Autowired UserService users) + throws InterruptedException { + ClientRegistration registration = CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("github").build(); + + Instant issuedAt = Instant.now(); + Instant expiresAt = issuedAt.plus(Duration.ofMinutes(5)); + OAuth2AccessToken token = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "1234", + issuedAt, expiresAt); + OAuth2AuthorizedClient result = new OAuth2AuthorizedClient(registration, "rob", token); + given(authorizedClients.authorize(any())).willReturn(Mono.just(result)); + + webServer.enqueue(new MockResponse().addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody( + """ + {"login": "rob_winch", "id": 1234, "name": "Rob Winch" } + """)); + + users.getAuthenticatedUser(); + + assertThat(webServer.takeRequest().getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + token.getTokenValue()); + } + +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationwebclient/ServerWebClientHttpInterfaceIntegrationConfiguration.java b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationwebclient/ServerWebClientHttpInterfaceIntegrationConfiguration.java new file mode 100644 index 0000000000..06e1970b5a --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/integrations/rest/configurationwebclient/ServerWebClientHttpInterfaceIntegrationConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.docs.features.integrations.rest.configurationwebclient; + +import okhttp3.mockwebserver.MockWebServer; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.docs.features.integrations.rest.clientregistrationid.UserService; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.client.support.OAuth2RestClientHttpServiceGroupConfigurer; +import org.springframework.security.oauth2.client.web.reactive.function.client.support.OAuth2WebClientHttpServiceGroupConfigurer; +import org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupConfigurer; +import org.springframework.web.service.registry.HttpServiceGroup; +import org.springframework.web.service.registry.ImportHttpServices; + +import static org.mockito.Mockito.mock; + +/** + * Documentation for {@link OAuth2RestClientHttpServiceGroupConfigurer}. + * @author Rob Winch + */ +@Configuration(proxyBeanMethods = false) +@ImportHttpServices(types = UserService.class, clientType = HttpServiceGroup.ClientType.WEB_CLIENT) +public class ServerWebClientHttpInterfaceIntegrationConfiguration { + + // tag::config[] + @Bean + OAuth2WebClientHttpServiceGroupConfigurer securityConfigurer( + ReactiveOAuth2AuthorizedClientManager manager) { + return OAuth2WebClientHttpServiceGroupConfigurer.from(manager); + } + // end::config[] + + @Bean + ReactiveOAuth2AuthorizedClientManager authorizedClientManager() { + return mock(ReactiveOAuth2AuthorizedClientManager.class); + } + + @Bean + WebClientHttpServiceGroupConfigurer groupConfigurer(MockWebServer server) { + return groups -> { + String baseUrl = server.url("").toString(); + groups + .forEachClient((group, builder) -> builder + .baseUrl(baseUrl) + .defaultHeader("Accept", "application/vnd.github.v3+json")); + }; + } + + @Bean + MockWebServer mockServer() { + return new MockWebServer(); + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/clientregistrationid/User.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/clientregistrationid/User.kt new file mode 100644 index 0000000000..bb99882ab6 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/clientregistrationid/User.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.kt.docs.features.integrations.rest.clientregistrationid + + +/** + * A user. + * @param login + * @param id + * @param name + * @author Rob Winch + * @see UserService + */ +@JvmRecord +data class User(val login: String, val id: Int, val name: String) diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/clientregistrationid/UserService.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/clientregistrationid/UserService.kt new file mode 100644 index 0000000000..e9dde63d6a --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/clientregistrationid/UserService.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.kt.docs.features.integrations.rest.clientregistrationid + +import org.springframework.security.oauth2.client.annotation.ClientRegistrationId +import org.springframework.web.service.annotation.GetExchange +import org.springframework.web.service.annotation.HttpExchange + +/** + * Demonstrates a service for {@link ClientRegistrationId} and HTTP Interface clients. + * @author Rob Winch + */ +@HttpExchange +interface UserService { + + // tag::getAuthenticatedUser[] + @GetExchange("/user") + @ClientRegistrationId("github") + fun getAuthenticatedUser() : User + // end::getAuthenticatedUser[] +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfiguration.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfiguration.kt new file mode 100644 index 0000000000..d30e88a122 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfiguration.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.kt.docs.features.integrations.rest.configurationrestclient + +import okhttp3.mockwebserver.MockWebServer +import org.mockito.Mockito +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.kt.docs.features.integrations.rest.clientregistrationid.UserService +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager +import org.springframework.security.oauth2.client.web.client.support.OAuth2RestClientHttpServiceGroupConfigurer +import org.springframework.web.client.RestClient +import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer +import org.springframework.web.service.registry.HttpServiceGroup +import org.springframework.web.service.registry.HttpServiceGroupConfigurer +import org.springframework.web.service.registry.HttpServiceGroupConfigurer.ClientCallback +import org.springframework.web.service.registry.ImportHttpServices + +/** + * Documentation for [OAuth2RestClientHttpServiceGroupConfigurer]. + * @author Rob Winch + */ +@Configuration(proxyBeanMethods = false) +@ImportHttpServices(types = [UserService::class]) +class RestClientHttpInterfaceIntegrationConfiguration { + // tag::config[] + @Bean + fun securityConfigurer(manager: OAuth2AuthorizedClientManager): OAuth2RestClientHttpServiceGroupConfigurer { + return OAuth2RestClientHttpServiceGroupConfigurer.from(manager) + } + // end::config[] + + @Bean + fun authorizedClientManager(): OAuth2AuthorizedClientManager? { + return Mockito.mock(OAuth2AuthorizedClientManager::class.java) + } + + @Bean + fun groupConfigurer(server: MockWebServer): RestClientHttpServiceGroupConfigurer { + return RestClientHttpServiceGroupConfigurer { groups: HttpServiceGroupConfigurer.Groups -> + groups.forEachClient(ClientCallback { group: HttpServiceGroup, builder: RestClient.Builder -> + builder + .baseUrl(server.url("").toString()) + }) + } + } + + @Bean + fun mockServer(): MockWebServer { + return MockWebServer() + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfigurationTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfigurationTests.kt new file mode 100644 index 0000000000..85a395cd33 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationrestclient/RestClientHttpInterfaceIntegrationConfigurationTests.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.kt.docs.features.integrations.rest.configurationrestclient + +import io.mockk.every +import io.mockk.mockkObject +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.security.config.oauth2.client.CommonOAuth2Provider +import org.springframework.security.kt.docs.features.integrations.rest.clientregistrationid.UserService +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager +import org.springframework.security.oauth2.core.OAuth2AccessToken +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.junit.jupiter.SpringExtension +import java.time.Duration +import java.time.Instant + +@ExtendWith(SpringExtension::class) +@ContextConfiguration(classes = [RestClientHttpInterfaceIntegrationConfiguration::class]) +internal class RestClientHttpInterfaceIntegrationConfigurationTests { + @Test + fun getAuthenticatedUser( + @Autowired webServer: MockWebServer, + @Autowired authorizedClients: OAuth2AuthorizedClientManager, + @Autowired users: UserService + ) { + val registration = CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("github").build() + + val issuedAt = Instant.now() + val expiresAt = issuedAt.plus(Duration.ofMinutes(5)) + val token = OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, "1234", + issuedAt, expiresAt + ) + val result = OAuth2AuthorizedClient(registration, "rob", token) + mockkObject(authorizedClients) + every { + authorizedClients.authorize(any()) + } returns result + + webServer.enqueue( + MockResponse().addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody( + """ + {"login": "rob_winch", "id": 1234, "name": "Rob Winch" } + """.trimIndent() + ) + ) + + users.getAuthenticatedUser() + + Assertions.assertThat(webServer.takeRequest().getHeader(HttpHeaders.AUTHORIZATION)) + .isEqualTo("Bearer " + token.getTokenValue()) + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationwebclient/ServerRestClientHttpInterfaceIntegrationConfigurationTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationwebclient/ServerRestClientHttpInterfaceIntegrationConfigurationTests.kt new file mode 100644 index 0000000000..9a5ed50ff9 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationwebclient/ServerRestClientHttpInterfaceIntegrationConfigurationTests.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.kt.docs.features.integrations.rest.configurationwebclient + +import io.mockk.every +import io.mockk.mockkObject +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.security.config.oauth2.client.CommonOAuth2Provider +import org.springframework.security.kt.docs.features.integrations.rest.clientregistrationid.UserService +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager +import org.springframework.security.oauth2.core.OAuth2AccessToken +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.junit.jupiter.SpringExtension +import reactor.core.publisher.Mono +import java.time.Duration +import java.time.Instant + +@ExtendWith(SpringExtension::class) +@ContextConfiguration(classes = [ServerWebClientHttpInterfaceIntegrationConfiguration::class]) +internal class ServerRestClientHttpInterfaceIntegrationConfigurationTests { + @Test + @Throws(InterruptedException::class) + fun getAuthenticatedUser( + @Autowired webServer: MockWebServer, + @Autowired authorizedClients: ReactiveOAuth2AuthorizedClientManager, + @Autowired users: UserService + ) { + val registration = CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("github").build() + + val issuedAt = Instant.now() + val expiresAt = issuedAt.plus(Duration.ofMinutes(5)) + val token = OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, "1234", + issuedAt, expiresAt + ) + val result = OAuth2AuthorizedClient(registration, "rob", token) + mockkObject(authorizedClients) + every { + authorizedClients.authorize(any()) + } returns Mono.just(result) + + webServer.enqueue( + MockResponse().addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody( + """ + {"login": "rob_winch", "id": 1234, "name": "Rob Winch" } + + """.trimIndent() + ) + ) + + users.getAuthenticatedUser() + + Assertions.assertThat(webServer.takeRequest().getHeader(HttpHeaders.AUTHORIZATION)) + .isEqualTo("Bearer " + token.getTokenValue()) + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationwebclient/ServerWebClientHttpInterfaceIntegrationConfiguration.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationwebclient/ServerWebClientHttpInterfaceIntegrationConfiguration.kt new file mode 100644 index 0000000000..89fa67348e --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationwebclient/ServerWebClientHttpInterfaceIntegrationConfiguration.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain clients copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.kt.docs.features.integrations.rest.configurationwebclient + +import okhttp3.mockwebserver.MockWebServer +import org.mockito.Mockito +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.kt.docs.features.integrations.rest.clientregistrationid.UserService +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager +import org.springframework.security.oauth2.client.web.client.support.OAuth2RestClientHttpServiceGroupConfigurer +import org.springframework.security.oauth2.client.web.reactive.function.client.support.OAuth2WebClientHttpServiceGroupConfigurer +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupConfigurer +import org.springframework.web.service.registry.HttpServiceGroup +import org.springframework.web.service.registry.HttpServiceGroupConfigurer +import org.springframework.web.service.registry.HttpServiceGroupConfigurer.ClientCallback +import org.springframework.web.service.registry.ImportHttpServices + +/** + * Documentation for [OAuth2RestClientHttpServiceGroupConfigurer]. + * @author Rob Winch + */ +@Configuration(proxyBeanMethods = false) +@ImportHttpServices(types = [UserService::class], clientType = HttpServiceGroup.ClientType.WEB_CLIENT) +class ServerWebClientHttpInterfaceIntegrationConfiguration { + // tag::config[] + @Bean + fun securityConfigurer( + manager: ReactiveOAuth2AuthorizedClientManager? + ): OAuth2WebClientHttpServiceGroupConfigurer { + return OAuth2WebClientHttpServiceGroupConfigurer.from(manager) + } + + // end::config[] + @Bean + fun authorizedClientManager(): ReactiveOAuth2AuthorizedClientManager? { + return Mockito.mock(ReactiveOAuth2AuthorizedClientManager::class.java) + } + + @Bean + fun groupConfigurer(server: MockWebServer): WebClientHttpServiceGroupConfigurer { + return WebClientHttpServiceGroupConfigurer { groups: HttpServiceGroupConfigurer.Groups? -> + val baseUrl = server.url("").toString() + groups!! + .forEachClient(ClientCallback { group: HttpServiceGroup?, builder: WebClient.Builder? -> + builder!! + .baseUrl(baseUrl) + .defaultHeader("Accept", "application/vnd.github.v3+json") + }) + } + } + + @Bean + fun mockServer(): MockWebServer { + return MockWebServer() + } +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/annotation/ClientRegistrationId.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/annotation/ClientRegistrationId.java new file mode 100644 index 0000000000..30fac85fd3 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/annotation/ClientRegistrationId.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * This annotation can be added to the method of an interface based HTTP client created + * using {@link org.springframework.web.service.invoker.HttpServiceProxyFactory} to + * automatically associate an OAuth token with the request. + * + * @author Rob Winch + * @since 7.0 + * @see org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor + */ +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ClientRegistrationId { + + /** + * Sets the client registration identifier. + * @return the client registration identifier + */ + @AliasFor("value") + String registrationId() default ""; + + /** + * The default attribute for this annotation. This is an alias for + * {@link #registrationId()}. For example, + * {@code @RegisteredOAuth2AuthorizedClient("login-client")} is equivalent to + * {@code @RegisteredOAuth2AuthorizedClient(registrationId="login-client")}. + * @return the client registration identifier + */ + @AliasFor("registrationId") + String value() default ""; + +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/ClientAttributes.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/ClientAttributes.java new file mode 100644 index 0000000000..6075ca6626 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/ClientAttributes.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.web; + +import java.util.Map; +import java.util.function.Consumer; + +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.util.Assert; + +/** + * Used for accessing the attribute that stores the the + * {@link ClientRegistration#getRegistrationId()}. This ensures that + * {@link org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor} + * aligns with all of ways of setting on both + * {@link org.springframework.web.client.RestClient} and + * {@link org.springframework.web.reactive.function.client.WebClient}. + * + * @see org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor + * @see org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver + * @see org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction + * @see org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction + */ +public final class ClientAttributes { + + private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = ClientRegistration.class.getName() + .concat(".CLIENT_REGISTRATION_ID"); + + /** + * Resolves the {@link ClientRegistration#getRegistrationId() clientRegistrationId} to + * be used to look up the {@link OAuth2AuthorizedClient}. + * @param attributes the to search + * @return the registration id to use. + */ + public static String resolveClientRegistrationId(Map attributes) { + return (String) attributes.get(CLIENT_REGISTRATION_ID_ATTR_NAME); + } + + /** + * Produces a Consumer that adds the {@link ClientRegistration#getRegistrationId() + * clientRegistrationId} to be used to look up the {@link OAuth2AuthorizedClient}. + * @param clientRegistrationId the {@link ClientRegistration#getRegistrationId() + * clientRegistrationId} to be used to look up the {@link OAuth2AuthorizedClient} + * @return the {@link Consumer} to populate the attributes + */ + public static Consumer> clientRegistrationId(String clientRegistrationId) { + Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty"); + return (attributes) -> attributes.put(CLIENT_REGISTRATION_ID_ATTR_NAME, clientRegistrationId); + } + + private ClientAttributes() { + } + +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessor.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessor.java new file mode 100644 index 0000000000..ec6f6bedac --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.web.client; + +import java.lang.reflect.Method; + +import org.jspecify.annotations.Nullable; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.security.oauth2.client.annotation.ClientRegistrationId; +import org.springframework.security.oauth2.client.web.ClientAttributes; +import org.springframework.web.service.invoker.HttpRequestValues; + +/** + * Invokes {@link ClientAttributes#clientRegistrationId(String)} with the value specified + * by {@link ClientRegistrationId} on the request. + * + * @author Rob Winch + * @since 7.0 + */ +public final class ClientRegistrationIdProcessor implements HttpRequestValues.Processor { + + public static ClientRegistrationIdProcessor DEFAULT_INSTANCE = new ClientRegistrationIdProcessor(); + + @Override + public void process(Method method, @Nullable Object[] arguments, HttpRequestValues.Builder builder) { + ClientRegistrationId registeredId = AnnotationUtils.findAnnotation(method, ClientRegistrationId.class); + if (registeredId != null) { + String registrationId = registeredId.registrationId(); + builder.configureAttributes(ClientAttributes.clientRegistrationId(registrationId)); + } + } + + private ClientRegistrationIdProcessor() { + } + +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/RequestAttributeClientRegistrationIdResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/RequestAttributeClientRegistrationIdResolver.java index f1031ae9c1..88f88fefd8 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/RequestAttributeClientRegistrationIdResolver.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/RequestAttributeClientRegistrationIdResolver.java @@ -23,7 +23,7 @@ import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.util.Assert; +import org.springframework.security.oauth2.client.web.ClientAttributes; /** * A strategy for resolving a {@code clientRegistrationId} from an intercepted request @@ -36,13 +36,9 @@ import org.springframework.util.Assert; public final class RequestAttributeClientRegistrationIdResolver implements OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver { - private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = RequestAttributeClientRegistrationIdResolver.class - .getName() - .concat(".clientRegistrationId"); - @Override public String resolve(HttpRequest request) { - return (String) request.getAttributes().get(CLIENT_REGISTRATION_ID_ATTR_NAME); + return ClientAttributes.resolveClientRegistrationId(request.getAttributes()); } /** @@ -54,8 +50,7 @@ public final class RequestAttributeClientRegistrationIdResolver * @return the {@link Consumer} to populate the attributes */ public static Consumer> clientRegistrationId(String clientRegistrationId) { - Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty"); - return (attributes) -> attributes.put(CLIENT_REGISTRATION_ID_ATTR_NAME, clientRegistrationId); + return ClientAttributes.clientRegistrationId(clientRegistrationId); } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/support/OAuth2RestClientHttpServiceGroupConfigurer.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/support/OAuth2RestClientHttpServiceGroupConfigurer.java new file mode 100644 index 0000000000..115c11ed72 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/support/OAuth2RestClientHttpServiceGroupConfigurer.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.web.client.support; + +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor; +import org.springframework.security.oauth2.client.web.client.OAuth2ClientHttpRequestInterceptor; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer; +import org.springframework.web.service.invoker.HttpRequestValues; + +/** + * Simplify adding OAuth2 support to interface based rest clients that use + * {@link RestClient}. + * + * It will add {@link OAuth2ClientHttpRequestInterceptor} to the {@link RestClient} and + * {@link ClientRegistrationIdProcessor} to the + * {@link org.springframework.web.service.invoker.HttpServiceProxyFactory}. + * + * @author Rob Winch + * @since 7.0 + */ +public final class OAuth2RestClientHttpServiceGroupConfigurer implements RestClientHttpServiceGroupConfigurer { + + private final HttpRequestValues.Processor processor = ClientRegistrationIdProcessor.DEFAULT_INSTANCE; + + private final ClientHttpRequestInterceptor interceptor; + + private OAuth2RestClientHttpServiceGroupConfigurer(ClientHttpRequestInterceptor interceptor) { + this.interceptor = interceptor; + } + + @Override + public void configureGroups(Groups groups) { + // @formatter:off + groups.forEachClient((group, client) -> + client.requestInterceptor(this.interceptor) + ); + groups.forEachProxyFactory((group, factory) -> + factory.httpRequestValuesProcessor(this.processor) + ); + // @formatter:on + } + + public static OAuth2RestClientHttpServiceGroupConfigurer from( + OAuth2AuthorizedClientManager authorizedClientManager) { + OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor( + authorizedClientManager); + return new OAuth2RestClientHttpServiceGroupConfigurer(interceptor); + } + +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java index 2d9be5ebf4..6831a46961 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java @@ -44,6 +44,7 @@ import org.springframework.security.oauth2.client.RemoveAuthorizedClientReactive import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.ClientAttributes; import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; @@ -104,13 +105,6 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements */ private static final String OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME = OAuth2AuthorizedClient.class.getName(); - /** - * The client request attribute name used to locate the - * {@link ClientRegistration#getRegistrationId()} - */ - private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = OAuth2AuthorizedClient.class.getName() - .concat(".CLIENT_REGISTRATION_ID"); - /** * The request attribute name used to locate the * {@link org.springframework.web.server.ServerWebExchange}. @@ -292,7 +286,7 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements * @return the {@link Consumer} to populate the attributes */ public static Consumer> clientRegistrationId(String clientRegistrationId) { - return (attributes) -> attributes.put(CLIENT_REGISTRATION_ID_ATTR_NAME, clientRegistrationId); + return ClientAttributes.clientRegistrationId(clientRegistrationId); } private static String clientRegistrationId(ClientRequest request) { @@ -300,7 +294,7 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements if (authorizedClient != null) { return authorizedClient.getClientRegistration().getRegistrationId(); } - return (String) request.attributes().get(CLIENT_REGISTRATION_ID_ATTR_NAME); + return ClientAttributes.resolveClientRegistrationId(request.attributes()); } /** diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java index db136ba11b..98c4370e25 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java @@ -50,6 +50,7 @@ import org.springframework.security.oauth2.client.RemoveAuthorizedClientOAuth2Au import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.ClientAttributes; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; @@ -136,9 +137,6 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement */ private static final String OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME = OAuth2AuthorizedClient.class.getName(); - private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = OAuth2AuthorizedClient.class.getName() - .concat(".CLIENT_REGISTRATION_ID"); - private static final String AUTHENTICATION_ATTR_NAME = Authentication.class.getName(); private static final String HTTP_SERVLET_REQUEST_ATTR_NAME = HttpServletRequest.class.getName(); @@ -311,7 +309,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement * @return the {@link Consumer} to populate the attributes */ public static Consumer> clientRegistrationId(String clientRegistrationId) { - return (attributes) -> attributes.put(CLIENT_REGISTRATION_ID_ATTR_NAME, clientRegistrationId); + return ClientAttributes.clientRegistrationId(clientRegistrationId); } /** @@ -536,7 +534,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement } static String getClientRegistrationId(Map attrs) { - return (String) attrs.get(CLIENT_REGISTRATION_ID_ATTR_NAME); + return ClientAttributes.resolveClientRegistrationId(attrs); } static Authentication getAuthentication(Map attrs) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/support/OAuth2WebClientHttpServiceGroupConfigurer.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/support/OAuth2WebClientHttpServiceGroupConfigurer.java new file mode 100644 index 0000000000..78bf566fa3 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/support/OAuth2WebClientHttpServiceGroupConfigurer.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.web.reactive.function.client.support; + +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor; +import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction; +import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupConfigurer; +import org.springframework.web.service.invoker.HttpRequestValues; + +/** + * Simplify adding OAuth2 support to interface based rest clients that use + * {@link WebClient}. + * + * @author Rob Winch + * @since 7.0 + */ +public final class OAuth2WebClientHttpServiceGroupConfigurer implements WebClientHttpServiceGroupConfigurer { + + private final HttpRequestValues.Processor processor = ClientRegistrationIdProcessor.DEFAULT_INSTANCE; + + private final ExchangeFilterFunction filter; + + private OAuth2WebClientHttpServiceGroupConfigurer(ExchangeFilterFunction filter) { + this.filter = filter; + } + + @Override + public void configureGroups(Groups groups) { + // @formatter:off + groups.forEachClient((group, client) -> + client.filter(this.filter) + ); + groups.forEachProxyFactory((group, factory) -> + factory.httpRequestValuesProcessor(this.processor) + ); + // @formatter:on + } + + /** + * Create an instance for Reactive web applications from the provided + * {@link ReactiveOAuth2AuthorizedClientManager}. + * + * It will add {@link ServerOAuth2AuthorizedClientExchangeFilterFunction} to the + * {@link WebClient} and {@link ClientRegistrationIdProcessor} to the + * {@link org.springframework.web.service.invoker.HttpServiceProxyFactory}. + * @param authorizedClientManager the manager to use. + * @return the {@link OAuth2WebClientHttpServiceGroupConfigurer}. + */ + public static OAuth2WebClientHttpServiceGroupConfigurer from( + ReactiveOAuth2AuthorizedClientManager authorizedClientManager) { + ServerOAuth2AuthorizedClientExchangeFilterFunction filter = new ServerOAuth2AuthorizedClientExchangeFilterFunction( + authorizedClientManager); + return new OAuth2WebClientHttpServiceGroupConfigurer(filter); + } + + /** + * Create an instance for Servlet based environments from the provided + * {@link OAuth2AuthorizedClientManager}. + * + * It will add {@link ServletOAuth2AuthorizedClientExchangeFilterFunction} to the + * {@link WebClient} and {@link ClientRegistrationIdProcessor} to the + * {@link org.springframework.web.service.invoker.HttpServiceProxyFactory}. + * @param authorizedClientManager the manager to use. + * @return the {@link OAuth2WebClientHttpServiceGroupConfigurer}. + */ + public static OAuth2WebClientHttpServiceGroupConfigurer from( + OAuth2AuthorizedClientManager authorizedClientManager) { + ServletOAuth2AuthorizedClientExchangeFilterFunction filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction( + authorizedClientManager); + return new OAuth2WebClientHttpServiceGroupConfigurer(filter); + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/AbstractMockServerClientRegistrationIdProcessorTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/AbstractMockServerClientRegistrationIdProcessorTests.java new file mode 100644 index 0000000000..c30ea20e99 --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/AbstractMockServerClientRegistrationIdProcessorTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.web.client; + +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.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.annotation.ClientRegistrationId; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.TestClientRegistrations; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; +import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.invoker.HttpExchangeAdapter; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Base class for integration testing {@link ClientRegistrationIdProcessor} with + * {@link MockWebServer}. + * + * @author Rob Winch + * @since 7.0 + */ +abstract class AbstractMockServerClientRegistrationIdProcessorTests { + + static final String REGISTRATION_ID = "okta"; + + private final MockWebServer server = new MockWebServer(); + + private OAuth2AccessToken accessToken; + + protected String baseUrl; + + protected OAuth2AuthorizedClient authorizedClient; + + @BeforeEach + void setup() throws IOException { + this.server.start(); + HttpUrl url = this.server.url("/range/"); + this.baseUrl = url.toString(); + ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() + .registrationId(REGISTRATION_ID) + .build(); + this.accessToken = TestOAuth2AccessTokens.scopes("read", "write"); + this.authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "user", this.accessToken); + } + + @AfterEach + void cleanup() throws IOException { + if (this.server != null) { + this.server.shutdown(); + } + } + + void testWithAdapter(HttpExchangeAdapter adapter) throws InterruptedException { + ClientRegistrationIdProcessor processor = ClientRegistrationIdProcessor.DEFAULT_INSTANCE; + HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder() + .exchangeAdapter(adapter) + .httpRequestValuesProcessor(processor) + .build(); + MessageClient messages = factory.createClient(MessageClient.class); + + this.server.enqueue(new MockResponse().setBody("Hello OAuth2!").setResponseCode(200)); + assertThat(messages.getMessage()).isEqualTo("Hello OAuth2!"); + + String authorizationHeader = this.server.takeRequest().getHeader(HttpHeaders.AUTHORIZATION); + assertOAuthTokenValue(authorizationHeader, this.accessToken); + + } + + private static void assertOAuthTokenValue(String value, OAuth2AccessToken accessToken) { + String tokenType = accessToken.getTokenType().getValue(); + String tokenValue = accessToken.getTokenValue(); + assertThat(value).isEqualTo("%s %s".formatted(tokenType, tokenValue)); + } + + interface MessageClient { + + @GetExchange("/message") + @ClientRegistrationId(REGISTRATION_ID) + String getMessage(); + + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorRestClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorRestClientTests.java new file mode 100644 index 0000000000..a096327bae --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorRestClientTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.web.client; + +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 org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.support.RestClientAdapter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +/** + * Runs tests of {@link ClientRegistrationIdProcessor} with {@link RestClient} to ensure + * that all the parts work together properly. + * + * @author Rob Winch + * @since 7.0 + */ +@ExtendWith(MockitoExtension.class) +class ClientRegistrationIdProcessorRestClientTests extends AbstractMockServerClientRegistrationIdProcessorTests { + + @Mock + private OAuth2AuthorizedClientManager authorizedClientManager; + + @Test + void clientRegistrationIdProcessorWorksWithRestClientAdapter() throws InterruptedException { + OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor( + this.authorizedClientManager); + RestClient.Builder builder = RestClient.builder().requestInterceptor(interceptor).baseUrl(this.baseUrl); + + ArgumentCaptor authorizeRequest = ArgumentCaptor.forClass(OAuth2AuthorizeRequest.class); + given(this.authorizedClientManager.authorize(authorizeRequest.capture())).willReturn(authorizedClient); + + testWithAdapter(RestClientAdapter.create(builder.build())); + + assertThat(authorizeRequest.getValue().getClientRegistrationId()).isEqualTo(REGISTRATION_ID); + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorTests.java new file mode 100644 index 0000000000..19ea292665 --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.web.client; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; + +import org.junit.jupiter.api.Test; + +import org.springframework.security.oauth2.client.annotation.ClientRegistrationId; +import org.springframework.security.oauth2.client.web.ClientAttributes; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.service.invoker.HttpRequestValues; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link ClientRegistrationIdProcessor}. + * + * @author Rob Winch + * @since 7.0 + * @see ClientRegistrationIdProcessorWebClientTests + * @see ClientRegistrationIdProcessorRestClientTests + */ +class ClientRegistrationIdProcessorTests { + + ClientRegistrationIdProcessor processor = ClientRegistrationIdProcessor.DEFAULT_INSTANCE; + + @Test + void processWhenClientRegistrationIdPresentThenSet() { + HttpRequestValues.Builder builder = HttpRequestValues.builder(); + Method hasClientRegistrationId = ReflectionUtils.findMethod(RestService.class, "hasClientRegistrationId"); + this.processor.process(hasClientRegistrationId, null, builder); + + String registrationId = ClientAttributes.resolveClientRegistrationId(builder.build().getAttributes()); + assertThat(registrationId).isEqualTo(RestService.REGISTRATION_ID); + } + + @Test + void processWhenMetaClientRegistrationIdPresentThenSet() { + HttpRequestValues.Builder builder = HttpRequestValues.builder(); + Method hasClientRegistrationId = ReflectionUtils.findMethod(RestService.class, "hasMetaClientRegistrationId"); + this.processor.process(hasClientRegistrationId, null, builder); + + String registrationId = ClientAttributes.resolveClientRegistrationId(builder.build().getAttributes()); + assertThat(registrationId).isEqualTo(RestService.REGISTRATION_ID); + } + + @Test + void processWhenNoClientRegistrationIdPresentThenNull() { + HttpRequestValues.Builder builder = HttpRequestValues.builder(); + Method hasClientRegistrationId = ReflectionUtils.findMethod(RestService.class, "noClientRegistrationId"); + this.processor.process(hasClientRegistrationId, null, builder); + + String registrationId = ClientAttributes.resolveClientRegistrationId(builder.build().getAttributes()); + assertThat(registrationId).isNull(); + } + + interface RestService { + + String REGISTRATION_ID = "registrationId"; + + @ClientRegistrationId(REGISTRATION_ID) + void hasClientRegistrationId(); + + @MetaClientRegistrationId + void hasMetaClientRegistrationId(); + + void noClientRegistrationId(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @ClientRegistrationId(RestService.REGISTRATION_ID) + @interface MetaClientRegistrationId { + + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorWebClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorWebClientTests.java new file mode 100644 index 0000000000..459264c92f --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/ClientRegistrationIdProcessorWebClientTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.web.client; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Mono; + +import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction; +import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.support.WebClientAdapter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Runs tests for {@link ClientRegistrationIdProcessor} with {@link WebClient} to ensure + * that all the parts work together properly. + * + * @author Rob Winch + * @since 7.0 + */ +@ExtendWith(MockitoExtension.class) +class ClientRegistrationIdProcessorWebClientTests extends AbstractMockServerClientRegistrationIdProcessorTests { + + @Test + void clientRegistrationIdProcessorWorksWithReactiveWebClient() throws InterruptedException { + ReactiveOAuth2AuthorizedClientManager authorizedClientManager = mock( + ReactiveOAuth2AuthorizedClientManager.class); + ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServerOAuth2AuthorizedClientExchangeFilterFunction( + authorizedClientManager); + + WebClient.Builder builder = WebClient.builder().filter(oauth2Client).baseUrl(this.baseUrl); + + ArgumentCaptor authorizeRequest = ArgumentCaptor.forClass(OAuth2AuthorizeRequest.class); + given(authorizedClientManager.authorize(authorizeRequest.capture())) + .willReturn(Mono.just(this.authorizedClient)); + + testWithAdapter(WebClientAdapter.create(builder.build())); + + assertThat(authorizeRequest.getValue().getClientRegistrationId()).isEqualTo(REGISTRATION_ID); + } + + @Test + void clientRegistrationIdProcessorWorksWithServletWebClient() throws InterruptedException { + OAuth2AuthorizedClientManager authorizedClientManager = mock(OAuth2AuthorizedClientManager.class); + + ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction( + authorizedClientManager); + + WebClient.Builder builder = WebClient.builder().filter(oauth2Client).baseUrl(this.baseUrl); + + ArgumentCaptor authorizeRequest = ArgumentCaptor.forClass(OAuth2AuthorizeRequest.class); + given(authorizedClientManager.authorize(authorizeRequest.capture())).willReturn(this.authorizedClient); + + testWithAdapter(WebClientAdapter.create(builder.build())); + + assertThat(authorizeRequest.getValue().getClientRegistrationId()).isEqualTo(REGISTRATION_ID); + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/support/OAuth2RestClientHttpServiceGroupConfigurerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/support/OAuth2RestClientHttpServiceGroupConfigurerTests.java new file mode 100644 index 0000000000..489b4a042d --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/client/support/OAuth2RestClientHttpServiceGroupConfigurerTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.web.client.support; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor; +import org.springframework.security.oauth2.client.web.client.OAuth2ClientHttpRequestInterceptor; +import org.springframework.web.client.RestClient; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; +import org.springframework.web.service.registry.HttpServiceGroupConfigurer; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; + +/** + * Tests {@link OAuth2RestClientHttpServiceGroupConfigurer}. + * + * @author Rob Winch + */ +@ExtendWith(MockitoExtension.class) +class OAuth2RestClientHttpServiceGroupConfigurerTests { + + @Mock + private OAuth2AuthorizedClientManager authoriedClientManager; + + @Mock + private HttpServiceGroupConfigurer.Groups groups; + + @Captor + ArgumentCaptor forProxyFactory; + + @Mock + private HttpServiceProxyFactory.Builder factoryBuilder; + + @Captor + private ArgumentCaptor> configureClient; + + @Mock + private RestClient.Builder clientBuilder; + + @Test + void configureGroupsConfigureProxyFactory() { + + OAuth2RestClientHttpServiceGroupConfigurer configurer = OAuth2RestClientHttpServiceGroupConfigurer + .from(this.authoriedClientManager); + + configurer.configureGroups(this.groups); + verify(this.groups).forEachProxyFactory(this.forProxyFactory.capture()); + + this.forProxyFactory.getValue().withProxyFactory(null, this.factoryBuilder); + + verify(this.factoryBuilder).httpRequestValuesProcessor(ClientRegistrationIdProcessor.DEFAULT_INSTANCE); + } + + @Test + void configureGroupsConfigureClient() { + OAuth2RestClientHttpServiceGroupConfigurer configurer = OAuth2RestClientHttpServiceGroupConfigurer + .from(this.authoriedClientManager); + + configurer.configureGroups(this.groups); + verify(this.groups).forEachClient(this.configureClient.capture()); + + this.configureClient.getValue().withClient(null, this.clientBuilder); + + verify(this.clientBuilder).requestInterceptor(any(OAuth2ClientHttpRequestInterceptor.class)); + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/support/OAuth2WebClientHttpServiceGroupConfigurerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/support/OAuth2WebClientHttpServiceGroupConfigurerTests.java new file mode 100644 index 0000000000..058d434386 --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/support/OAuth2WebClientHttpServiceGroupConfigurerTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.web.reactive.function.client.support; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; +import org.springframework.web.service.registry.HttpServiceGroupConfigurer; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; + +/** + * Tests {@link OAuth2WebClientHttpServiceGroupConfigurer}. + * + * @author Rob Winch + */ +@ExtendWith(MockitoExtension.class) +class OAuth2WebClientHttpServiceGroupConfigurerTests { + + @Mock + private OAuth2AuthorizedClientManager authoriedClientManager; + + @Mock + private HttpServiceGroupConfigurer.Groups groups; + + @Captor + ArgumentCaptor forProxyFactory; + + @Mock + private HttpServiceProxyFactory.Builder factoryBuilder; + + @Captor + private ArgumentCaptor> configureClient; + + @Mock + private WebClient.Builder clientBuilder; + + @Test + void configureGroupsConfigureProxyFactory() { + + OAuth2WebClientHttpServiceGroupConfigurer configurer = OAuth2WebClientHttpServiceGroupConfigurer + .from(this.authoriedClientManager); + + configurer.configureGroups(this.groups); + verify(this.groups).forEachProxyFactory(this.forProxyFactory.capture()); + + this.forProxyFactory.getValue().withProxyFactory(null, this.factoryBuilder); + + verify(this.factoryBuilder).httpRequestValuesProcessor(ClientRegistrationIdProcessor.DEFAULT_INSTANCE); + } + + @Test + void configureGroupsConfigureClient() { + OAuth2WebClientHttpServiceGroupConfigurer configurer = OAuth2WebClientHttpServiceGroupConfigurer + .from(this.authoriedClientManager); + + configurer.configureGroups(this.groups); + verify(this.groups).forEachClient(this.configureClient.capture()); + + this.configureClient.getValue().withClient(null, this.clientBuilder); + + verify(this.clientBuilder).filter(any(ExchangeFilterFunction.class)); + } + +} From c2c84c42435ec7dda4ee242ee21689235957a4e4 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Mon, 2 Jun 2025 16:19:01 +0700 Subject: [PATCH 140/149] Update HttpSecurity javadoc Signed-off-by: Tran Ngoc Nhan --- .../config/annotation/web/builders/HttpSecurity.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 0727ba33bd..ed84d98016 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -107,7 +107,7 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector; * A {@link HttpSecurity} is similar to Spring Security's XML <http> element in the * namespace configuration. It allows configuring web based security for specific http * requests. By default it will be applied to all requests, but can be restricted using - * {@link #requestMatcher(RequestMatcher)} or other similar methods. + * {@link #authorizeHttpRequests(Customizer)} or other similar methods. * *

Example Usage

* @@ -124,7 +124,12 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector; * * @Bean * public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - * http.authorizeHttpRequests().requestMatchers("/**").hasRole("USER").and().formLogin(); + * http + * .authorizeHttpRequests((authorizeHttpRequests) -> + * authorizeHttpRequests + * .requestMatchers("/**").hasRole("USER") + * ) + * .formLogin(withDefaults()); * return http.build(); * } * From 9cf563891419f09d93c9cfb12155a7785e5673e8 Mon Sep 17 00:00:00 2001 From: chanbinme Date: Sun, 15 Jun 2025 23:50:38 +0900 Subject: [PATCH 141/149] Add null check for authentication token in JwtAuthenticationProvider Add Assert.notNull validation to ensure the authentication token returned by jwtAuthenticationConverter is not null, preventing potential NullPointerException in subsequent operations. Signed-off-by: chanbinme --- .../JwtAuthenticationProvider.java | 1 + .../JwtAuthenticationProviderTests.java | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.java index cdca4ffc0c..95d7574eb4 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.java @@ -87,6 +87,7 @@ public final class JwtAuthenticationProvider implements AuthenticationProvider { BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication; Jwt jwt = getJwt(bearer); AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt); + Assert.notNull(token, "token cannot be null"); if (token.getDetails() == null) { token.setDetails(bearer.getDetails()); } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java index b4438ba28f..6c5912df10 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java @@ -35,8 +35,7 @@ import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -152,6 +151,19 @@ public class JwtAuthenticationProviderTests { // @formatter:on } + @Test + public void authenticateWhenConverterReturnsNullThenThrowException() { + BearerTokenAuthenticationToken token = this.authentication(); + Jwt jwt = TestJwts.jwt().build(); + given(this.jwtDecoder.decode("token")).willReturn(jwt); + given(this.jwtAuthenticationConverter.convert(jwt)).willReturn(null); + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.provider.authenticate(token)) + .withMessageContaining("token cannot be null"); + // @formatter:on + } + @Test public void supportsWhenBearerTokenAuthenticationTokenThenReturnsTrue() { assertThat(this.provider.supports(BearerTokenAuthenticationToken.class)).isTrue(); From 18010f9914e3ab938b51f459c83451e3721fedf2 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 17 Jun 2025 13:20:22 -0500 Subject: [PATCH 142/149] Fix JwtAuthenticationProvider Checkstyle Issue gh-17251 --- .../authentication/JwtAuthenticationProviderTests.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java index 6c5912df10..c202dab8cd 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java @@ -35,7 +35,9 @@ import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes; -import static org.assertj.core.api.Assertions.*; +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.BDDMockito.given; import static org.mockito.Mockito.mock; From 709f5db0e5d0b86806c45f9a54c9e57ffdb19b97 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Mon, 2 Jun 2025 16:18:25 +0700 Subject: [PATCH 143/149] Polish Webauthn4JRelyingPartyOperations Signed-off-by: Tran Ngoc Nhan --- .../management/Webauthn4JRelyingPartyOperations.java | 5 +---- .../management/Webauthn4jRelyingPartyOperationsTests.java | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java b/web/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java index 59d0729288..74ea3fa5f6 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java @@ -199,7 +199,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe } private static List credentialDescriptors(List credentialRecords) { - List result = new ArrayList(); + List result = new ArrayList<>(); for (CredentialRecord credentialRecord : credentialRecords) { Bytes id = Bytes.fromBase64(credentialRecord.getCredentialId().toBase64UrlString()); PublicKeyCredentialDescriptor credentialDescriptor = PublicKeyCredentialDescriptor.builder() @@ -372,9 +372,6 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe Authenticator authenticator = new AuthenticatorImpl(data, attestationObject.getAttestationStatement(), credentialRecord.getSignatureCount()); - if (authenticator == null) { - throw new IllegalStateException("No authenticator found"); - } Set origins = toOrigins(); Challenge challenge = new DefaultChallenge(requestOptions.getChallenge().getBytes()); // FIXME: should populate this diff --git a/web/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java b/web/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java index 57c551c215..5b4ca74145 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java @@ -138,7 +138,7 @@ class Webauthn4jRelyingPartyOperationsTests { @Test void createPublicKeyCredentialCreationOptionsWhenAnonymousThenIllegalArgumentException() { AnonymousAuthenticationToken anonymous = new AnonymousAuthenticationToken("key", "notAuthenticated", - Set.of(() -> "ROLE_ANOYMOUS")); + Set.of(() -> "ROLE_ANONYMOUS")); assertThatIllegalArgumentException() .isThrownBy(() -> this.rpOperations.createPublicKeyCredentialCreationOptions( new ImmutablePublicKeyCredentialCreationOptionsRequest(anonymous))); From ee09215f89d5e8edc4de8ce0de5ae14525aa6c52 Mon Sep 17 00:00:00 2001 From: Suraj Bhadrike Date: Fri, 9 May 2025 01:43:55 +0530 Subject: [PATCH 144/149] Add NimbusJwtEncoder Builders Closes gh-16267 Signed-off-by: Suraj Bhadrike --- .../security/oauth2/jwt/NimbusJwtEncoder.java | 267 +++++++++++++++++- .../oauth2/jwt/NimbusJwtEncoderTests.java | 190 +++++++++++++ 2 files changed, 454 insertions(+), 3 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java index fb0468fa9b..8e1b7f57e6 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java @@ -18,6 +18,8 @@ package org.springframework.security.oauth2.jwt; import java.net.URI; import java.net.URL; +import java.security.KeyPair; +import java.security.interfaces.ECPublicKey; import java.time.Instant; import java.util.ArrayList; import java.util.Date; @@ -25,19 +27,28 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import javax.crypto.SecretKey; + import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.crypto.factories.DefaultJWSSignerFactory; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKMatcher; import com.nimbusds.jose.jwk.JWKSelector; +import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.KeyType; import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.OctetSequenceKey; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.produce.JWSSignerFactory; @@ -47,6 +58,7 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import org.springframework.core.convert.converter.Converter; +import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -83,6 +95,8 @@ public final class NimbusJwtEncoder implements JwtEncoder { private static final JWSSignerFactory JWS_SIGNER_FACTORY = new DefaultJWSSignerFactory(); + private JwsHeader jwsHeader; + private final Map jwsSigners = new ConcurrentHashMap<>(); private final JWKSource jwkSource; @@ -119,14 +133,16 @@ public final class NimbusJwtEncoder implements JwtEncoder { this.jwkSelector = jwkSelector; } + public void setJwsHeader(JwsHeader jwsHeader) { + this.jwsHeader = jwsHeader; + } + @Override public Jwt encode(JwtEncoderParameters parameters) throws JwtEncodingException { Assert.notNull(parameters, "parameters cannot be null"); JwsHeader headers = parameters.getJwsHeader(); - if (headers == null) { - headers = DEFAULT_JWS_HEADER; - } + headers = (headers != null) ? headers : (this.jwsHeader != null) ? this.jwsHeader : DEFAULT_JWS_HEADER; JwtClaimsSet claims = parameters.getClaims(); JWK jwk = selectJwk(headers); @@ -369,4 +385,249 @@ public final class NimbusJwtEncoder implements JwtEncoder { } } + /** + * Creates a builder for constructing a {@link NimbusJwtEncoder} using the provided + * {@link SecretKey}. + * @param secretKey the {@link SecretKey} to use for signing JWTs + * @return a {@link SecretKeyJwtEncoderBuilder} for further configuration + * @since 7.0 + */ + public static SecretKeyJwtEncoderBuilder withSecretKey(SecretKey secretKey) { + Assert.notNull(secretKey, "secretKey cannot be null"); + return new SecretKeyJwtEncoderBuilder(secretKey); + } + + /** + * Creates a builder for constructing a {@link NimbusJwtEncoder} using the provided + * {@link KeyPair}. The key pair must contain either an {@link RSAKey} or an + * {@link ECKey}. + * @param keyPair the {@link KeyPair} to use for signing JWTs + * @return a {@link KeyPairJwtEncoderBuilder} for further configuration + * @since 7.0 + */ + public static KeyPairJwtEncoderBuilder withKeyPair(KeyPair keyPair) { + Assert.isTrue(keyPair != null && keyPair.getPrivate() != null && keyPair.getPublic() != null, + "keyPair, its private key, and public key must not be null"); + Assert.isTrue( + keyPair.getPrivate() instanceof java.security.interfaces.RSAKey + || keyPair.getPrivate() instanceof java.security.interfaces.ECKey, + "keyPair must be an RSAKey or an ECKey"); + if (keyPair.getPrivate() instanceof java.security.interfaces.RSAKey) { + return new RsaKeyPairJwtEncoderBuilder(keyPair); + } + if (keyPair.getPrivate() instanceof java.security.interfaces.ECKey) { + return new EcKeyPairJwtEncoderBuilder(keyPair); + } + throw new IllegalArgumentException("keyPair must be an RSAKey or an ECKey"); + } + + /** + * A builder for creating {@link NimbusJwtEncoder} instances configured with a + * {@link SecretKey}. + * + * @since 7.0 + */ + public static final class SecretKeyJwtEncoderBuilder { + + private final SecretKey secretKey; + + private String keyId; + + private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256; + + private SecretKeyJwtEncoderBuilder(SecretKey secretKey) { + this.secretKey = secretKey; + } + + /** + * Sets the JWS algorithm to use for signing. Defaults to + * {@link JWSAlgorithm#HS256}. Must be an HMAC-based algorithm (HS256, HS384, or + * HS512). + * @param macAlgorithm the {@link MacAlgorithm} to use + * @return this builder instance for method chaining + */ + public SecretKeyJwtEncoderBuilder macAlgorithm(MacAlgorithm macAlgorithm) { + Assert.notNull(macAlgorithm, "macAlgorithm cannot be null"); + Assert.state(JWSAlgorithm.Family.HMAC_SHA.contains(this.jwsAlgorithm), + () -> "The algorithm '" + this.jwsAlgorithm + "' is not compatible with a SecretKey. " + + "Please use one of the HS256, HS384, or HS512 algorithms."); + + this.jwsAlgorithm = JWSAlgorithm.parse(macAlgorithm.getName()); + return this; + } + + /** + * Sets the key ID ({@code kid}) to be included in the JWK and potentially the JWS + * header. + * @param keyId the key identifier + * @return this builder instance for method chaining + */ + public SecretKeyJwtEncoderBuilder keyId(String keyId) { + this.keyId = keyId; + return this; + } + + /** + * Builds the {@link NimbusJwtEncoder} instance. + * @return the configured {@link NimbusJwtEncoder} + * @throws IllegalStateException if the configured JWS algorithm is not compatible + * with a {@link SecretKey}. + */ + public NimbusJwtEncoder build() { + this.jwsAlgorithm = (this.jwsAlgorithm != null) ? this.jwsAlgorithm : JWSAlgorithm.HS256; + + OctetSequenceKey.Builder builder = new OctetSequenceKey.Builder(this.secretKey).keyUse(KeyUse.SIGNATURE) + .algorithm(this.jwsAlgorithm) + .keyID(this.keyId); + + OctetSequenceKey jwk = builder.build(); + JWKSource jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk)); + NimbusJwtEncoder encoder = new NimbusJwtEncoder(jwkSource); + encoder.setJwsHeader(JwsHeader.with(MacAlgorithm.from(this.jwsAlgorithm.getName())).build()); + return encoder; + } + + } + + /** + * A builder for creating {@link NimbusJwtEncoder} instances configured with a + * {@link KeyPair}. + * + * @since 7.0 + */ + public abstract static class KeyPairJwtEncoderBuilder { + + private final KeyPair keyPair; + + private String keyId; + + private JWSAlgorithm jwsAlgorithm; + + private KeyPairJwtEncoderBuilder(KeyPair keyPair) { + this.keyPair = keyPair; + } + + /** + * Sets the JWS algorithm to use for signing. Must be compatible with the key type + * (RSA or EC). If not set, a default algorithm will be chosen based on the key + * type (e.g., RS256 for RSA, ES256 for EC). + * @param signatureAlgorithm the {@link SignatureAlgorithm} to use + * @return this builder instance for method chaining + */ + public KeyPairJwtEncoderBuilder signatureAlgorithm(SignatureAlgorithm signatureAlgorithm) { + Assert.notNull(signatureAlgorithm, "signatureAlgorithm cannot be null"); + this.jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName()); + return this; + } + + /** + * Sets the key ID ({@code kid}) to be included in the JWK and potentially the JWS + * header. + * @param keyId the key identifier + * @return this builder instance for method chaining + */ + public KeyPairJwtEncoderBuilder keyId(String keyId) { + this.keyId = keyId; + return this; + } + + /** + * Builds the {@link NimbusJwtEncoder} instance. + * @return the configured {@link NimbusJwtEncoder} + * @throws IllegalStateException if the key type is unsupported or the configured + * JWS algorithm is not compatible with the key type. + * @throws JwtEncodingException if the key is invalid (e.g., EC key with unknown + * curve) + */ + public NimbusJwtEncoder build() { + this.keyId = (this.keyId != null) ? this.keyId : UUID.randomUUID().toString(); + JWK jwk = buildJwk(); + JWKSource jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk)); + NimbusJwtEncoder encoder = new NimbusJwtEncoder(jwkSource); + JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.from(this.jwsAlgorithm.getName())) + .keyId(jwk.getKeyID()) + .build(); + encoder.setJwsHeader(jwsHeader); + return encoder; + } + + protected abstract JWK buildJwk(); + + } + + /** + * A builder for creating {@link NimbusJwtEncoder} instances configured with a + * {@link KeyPair}. + * + * @since 7.0 + */ + public static final class RsaKeyPairJwtEncoderBuilder extends KeyPairJwtEncoderBuilder { + + private RsaKeyPairJwtEncoderBuilder(KeyPair keyPair) { + super(keyPair); + } + + @Override + protected JWK buildJwk() { + if (super.jwsAlgorithm == null) { + super.jwsAlgorithm = JWSAlgorithm.RS256; + } + Assert.state(JWSAlgorithm.Family.RSA.contains(super.jwsAlgorithm), + () -> "The algorithm '" + super.jwsAlgorithm + "' is not compatible with an RSAKey. " + + "Please use one of the RS256, RS384, RS512, PS256, PS384, or PS512 algorithms."); + + RSAKey.Builder builder = new RSAKey.Builder( + (java.security.interfaces.RSAPublicKey) super.keyPair.getPublic()) + .privateKey(super.keyPair.getPrivate()) + .keyID(super.keyId) + .keyUse(KeyUse.SIGNATURE) + .algorithm(super.jwsAlgorithm); + return builder.build(); + } + + } + + /** + * A builder for creating {@link NimbusJwtEncoder} instances configured with a + * {@link KeyPair}. + * + * @since 7.0 + */ + public static final class EcKeyPairJwtEncoderBuilder extends KeyPairJwtEncoderBuilder { + + private EcKeyPairJwtEncoderBuilder(KeyPair keyPair) { + super(keyPair); + } + + @Override + protected JWK buildJwk() { + if (super.jwsAlgorithm == null) { + super.jwsAlgorithm = JWSAlgorithm.ES256; + } + Assert.state(JWSAlgorithm.Family.EC.contains(super.jwsAlgorithm), + () -> "The algorithm '" + super.jwsAlgorithm + "' is not compatible with an ECKey. " + + "Please use one of the ES256, ES384, or ES512 algorithms."); + + ECPublicKey publicKey = (ECPublicKey) super.keyPair.getPublic(); + Curve curve = Curve.forECParameterSpec(publicKey.getParams()); + if (curve == null) { + throw new JwtEncodingException("Unable to determine Curve for EC public key."); + } + + com.nimbusds.jose.jwk.ECKey.Builder builder = new com.nimbusds.jose.jwk.ECKey.Builder(curve, publicKey) + .privateKey(super.keyPair.getPrivate()) + .keyUse(KeyUse.SIGNATURE) + .keyID(super.keyId) + .algorithm(super.jwsAlgorithm); + + try { + return builder.build(); + } + catch (IllegalStateException ex) { + throw new IllegalArgumentException("Failed to build ECKey: " + ex.getMessage(), ex); + } + } + + } + } diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java index ab17156eac..2abbaebf33 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java @@ -16,12 +16,20 @@ package org.springframework.security.oauth2.jwt; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.UUID; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.KeySourceException; @@ -48,6 +56,7 @@ import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; 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; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; @@ -344,6 +353,187 @@ public class NimbusJwtEncoderTests { verifyNoInteractions(selector); } + @Test + void secretKeyBuilderWithDefaultAlgorithm() { + SecretKey secretKey = new SecretKeySpec("thisIsASecretKeyUsedForTesting12345".getBytes(), "HMAC"); + JwtClaimsSet claims = buildClaims(); + + NimbusJwtEncoder encoder = NimbusJwtEncoder.withSecretKey(secretKey).build(); + Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); + + assertThat(jwt).isNotNull(); + assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("HS256"); + assertThatNoException().isThrownBy(jwt::getClaims); + assertJwt(jwt); + } + + @Test + void secretKeyBuilderWithKeyId() { + SecretKey secretKey = new SecretKeySpec("thisIsASecretKeyUsedForTesting12345".getBytes(), "HMAC"); + String keyId = "test-key-id"; + JwtClaimsSet claims = buildClaims(); + + NimbusJwtEncoder encoder = NimbusJwtEncoder.withSecretKey(secretKey).keyId(keyId).build(); + Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); + + assertThat(jwt).isNotNull(); + assertThat(jwt.getHeaders().get("kid").toString()).isEqualTo(keyId); + assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("HS256"); + assertThatNoException().isThrownBy(jwt::getClaims); + assertJwt(jwt); + } + + @Test + void secretKeyBuilderWithCustomJwkSelector() { + SecretKey secretKey = new SecretKeySpec("thisIsASecretKeyUsedForTesting12345".getBytes(), "HMAC"); + String keyId = "test-key-id"; + JwtClaimsSet claims = buildClaims(); + + NimbusJwtEncoder encoder = NimbusJwtEncoder.withSecretKey(secretKey).keyId(keyId).build(); + Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); + + assertThat(jwt).isNotNull(); + assertThat(jwt.getHeaders().get("kid")).isEqualTo(keyId); + assertThat(jwt.getClaims()).containsEntry("sub", "subject"); + assertThatNoException().isThrownBy(() -> jwt.getClaims()); + assertJwt(jwt); + } + + @Test + void secretKeyBuilderWithCustomHeaders() { + SecretKey secretKey = new SecretKeySpec("thisIsASecretKeyUsedForTesting12345".getBytes(), "HMAC"); + JwtClaimsSet claims = buildClaims(); + JwsHeader headers = JwsHeader.with(org.springframework.security.oauth2.jose.jws.MacAlgorithm.HS256) + .type("JWT") + .contentType("application/jwt") + .build(); + + NimbusJwtEncoder encoder = NimbusJwtEncoder.withSecretKey(secretKey).build(); + Jwt jwt = encoder.encode(JwtEncoderParameters.from(headers, claims)); + + assertThat(jwt).isNotNull(); + assertThat(jwt.getHeaders().get("typ").toString()).isEqualTo("JWT"); + assertThat(jwt.getHeaders().get("cty").toString()).isEqualTo("application/jwt"); + assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("HS256"); + assertThatNoException().isThrownBy(() -> jwt.getClaims()); + assertJwt(jwt); + } + + @Test + void keyPairBuilderWithRsaDefaultAlgorithm() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + JwtClaimsSet claims = buildClaims(); + + NimbusJwtEncoder encoder = NimbusJwtEncoder.withKeyPair(keyPair).build(); + Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); + + assertThat(jwt).isNotNull(); + assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("RS256"); + assertThat(jwt.getSubject()).isEqualTo(claims.getSubject()); + assertThat(jwt.getAudience()).isEqualTo(claims.getAudience()); + assertThatNoException().isThrownBy(() -> jwt.getClaims()); + assertJwt(jwt); + } + + @Test + void keyPairBuilderWithRsaCustomAlgorithm() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + JwtClaimsSet claims = buildClaims(); + + NimbusJwtEncoder encoder = NimbusJwtEncoder.withKeyPair(keyPair) + .signatureAlgorithm(SignatureAlgorithm.RS512) + .build(); + Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); + + assertThat(jwt).isNotNull(); + assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("RS512"); + assertThat(jwt.getSubject()).isEqualTo(claims.getSubject()); + assertThatNoException().isThrownBy(() -> jwt.getClaims()); + assertJwt(jwt); + } + + @Test + void keyPairBuilderWithEcDefaultAlgorithm() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); + keyPairGenerator.initialize(256); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + JwtClaimsSet claims = buildClaims(); + + NimbusJwtEncoder encoder = NimbusJwtEncoder.withKeyPair(keyPair).build(); + Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); + + assertThat(jwt).isNotNull(); + assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("ES256"); + assertThat(jwt.getSubject()).isEqualTo(claims.getSubject()); + assertThatNoException().isThrownBy(() -> jwt.getClaims()); + assertJwt(jwt); + } + + @Test + void keyPairBuilderWithEcCustomAlgorithm() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); + keyPairGenerator.initialize(256); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + NimbusJwtEncoder encoder = NimbusJwtEncoder.withKeyPair(keyPair) + .keyId(UUID.randomUUID().toString()) + .signatureAlgorithm(SignatureAlgorithm.ES256) + .build(); + + JwtClaimsSet claims = buildClaims(); + Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); + + assertThat(jwt).isNotNull(); + assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("ES256"); + assertThatNoException().isThrownBy(() -> jwt.getClaims()); + assertJwt(jwt); + } + + @Test + void keyPairBuilderWithKeyId() throws Exception { // d + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + String keyId = "test-key-id"; + JwtClaimsSet claims = buildClaims(); + + NimbusJwtEncoder encoder = NimbusJwtEncoder.withKeyPair(keyPair).keyId(keyId).build(); + Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); + + assertThat(jwt).isNotNull(); + assertThat(jwt.getHeaders().get("kid")).isEqualTo(keyId); + assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("RS256"); + assertThatNoException().isThrownBy(() -> jwt.getClaims()); + } + + private JwtClaimsSet buildClaims() { + Instant now = Instant.now(); + return JwtClaimsSet.builder() + .issuer("https://example.com") + .subject("subject") + .audience(Collections.singletonList("audience")) + .issuedAt(now) + .notBefore(now) + .expiresAt(now.plus(1, ChronoUnit.HOURS)) + .id(UUID.randomUUID().toString()) + .claim("custom", "value") + .build(); + } + + private static void assertJwt(Jwt jwt) { + assertThat(jwt.getIssuer().toString()).isEqualTo("https://example.com"); + assertThat(jwt.getSubject()).isEqualTo("subject"); + assertThat(jwt.getAudience()).containsExactly("audience"); + assertThat(jwt.getIssuedAt()).isNotNull(); + assertThat(jwt.getNotBefore()).isNotNull(); + assertThat(jwt.getExpiresAt()).isNotNull(); + assertThat(jwt.getId()).isNotNull(); + assertThat(jwt.getClaim("custom").toString()).isEqualTo("value"); + } + private static final class JwkListResultCaptor implements Answer> { private List result; From 676b44ebb052d4600bc99e6ecb3fb4f656b85a8a Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 17 Jun 2025 15:45:41 -0600 Subject: [PATCH 145/149] Polish NimbusJwtEncoder Builders - Simplify withKeyPair methods to match withPublicKey convention in NimbusJwtDecoder - Update tests to confirm support of other algorithms - Update constructor to apply additional JWK properties to the default header - Deduce the possibly algorithms for a given key based on curve and key size - Remove algorithm method from EC builder since the algorithm is determined by the Curve of the EC Key Issue gh-16267 Co-Authored-By: Suraj Bhadrike --- .../security/oauth2/jwt/JWKS.java | 87 ++++++ .../security/oauth2/jwt/NimbusJwtEncoder.java | 288 +++++++++--------- .../oauth2/jwt/NimbusJwtEncoderTests.java | 213 ++++++------- 3 files changed, 323 insertions(+), 265 deletions(-) create mode 100644 oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JWKS.java diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JWKS.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JWKS.java new file mode 100644 index 0000000000..8596749bc2 --- /dev/null +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JWKS.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.jwt; + +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Date; +import java.util.Set; + +import javax.crypto.SecretKey; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.crypto.impl.ECDSA; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.KeyOperation; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.OctetSequenceKey; +import com.nimbusds.jose.jwk.RSAKey; + +final class JWKS { + + private JWKS() { + + } + + static OctetSequenceKey.Builder signing(SecretKey key) throws JOSEException { + Date issued = new Date(); + return new OctetSequenceKey.Builder(key).keyOperations(Set.of(KeyOperation.SIGN)) + .keyUse(KeyUse.SIGNATURE) + .algorithm(JWSAlgorithm.HS256) + .keyIDFromThumbprint() + .issueTime(issued) + .notBeforeTime(issued); + } + + static ECKey.Builder signingWithEc(ECPublicKey pub, ECPrivateKey key) throws JOSEException { + Date issued = new Date(); + Curve curve = Curve.forECParameterSpec(pub.getParams()); + JWSAlgorithm algorithm = computeAlgorithm(curve); + return new ECKey.Builder(curve, pub).privateKey(key) + .keyOperations(Set.of(KeyOperation.SIGN)) + .keyUse(KeyUse.SIGNATURE) + .algorithm(algorithm) + .keyIDFromThumbprint() + .issueTime(issued) + .notBeforeTime(issued); + } + + private static JWSAlgorithm computeAlgorithm(Curve curve) { + try { + return ECDSA.resolveAlgorithm(curve); + } + catch (JOSEException ex) { + throw new IllegalArgumentException(ex); + } + } + + static RSAKey.Builder signingWithRsa(RSAPublicKey pub, RSAPrivateKey key) throws JOSEException { + Date issued = new Date(); + return new RSAKey.Builder(pub).privateKey(key) + .keyUse(KeyUse.SIGNATURE) + .keyOperations(Set.of(KeyOperation.SIGN)) + .algorithm(JWSAlgorithm.RS256) + .keyIDFromThumbprint() + .issueTime(issued) + .notBeforeTime(issued); + } + +} diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java index 8e1b7f57e6..8fd1ada518 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java @@ -19,7 +19,10 @@ package org.springframework.security.oauth2.jwt; import java.net.URI; import java.net.URL; import java.security.KeyPair; +import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; import java.time.Instant; import java.util.ArrayList; import java.util.Date; @@ -27,8 +30,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import javax.crypto.SecretKey; @@ -37,6 +40,7 @@ import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.crypto.MACSigner; import com.nimbusds.jose.crypto.factories.DefaultJWSSignerFactory; import com.nimbusds.jose.jwk.Curve; import com.nimbusds.jose.jwk.ECKey; @@ -58,11 +62,14 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import org.springframework.core.convert.converter.Converter; +import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import org.springframework.util.function.ThrowingBiFunction; +import org.springframework.util.function.ThrowingFunction; /** * An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT) using the @@ -74,6 +81,8 @@ import org.springframework.util.StringUtils; * NOTE: This implementation uses the Nimbus JOSE + JWT SDK. * * @author Joe Grandja + * @author Josh Cummings + * @author Suraj Bhadrike * @since 5.6 * @see JwtEncoder * @see com.nimbusds.jose.jwk.source.JWKSource @@ -95,7 +104,7 @@ public final class NimbusJwtEncoder implements JwtEncoder { private static final JWSSignerFactory JWS_SIGNER_FACTORY = new DefaultJWSSignerFactory(); - private JwsHeader jwsHeader; + private final JwsHeader defaultJwsHeader; private final Map jwsSigners = new ConcurrentHashMap<>(); @@ -114,10 +123,35 @@ public final class NimbusJwtEncoder implements JwtEncoder { * @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource} */ public NimbusJwtEncoder(JWKSource jwkSource) { + this.defaultJwsHeader = DEFAULT_JWS_HEADER; Assert.notNull(jwkSource, "jwkSource cannot be null"); this.jwkSource = jwkSource; } + private NimbusJwtEncoder(JWK jwk) { + Assert.notNull(jwk, "jwk cannot be null"); + this.jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk)); + JwsAlgorithm algorithm = SignatureAlgorithm.from(jwk.getAlgorithm().getName()); + if (algorithm == null) { + algorithm = MacAlgorithm.from(jwk.getAlgorithm().getName()); + } + Assert.notNull(algorithm, "Failed to derive supported algorithm from " + jwk.getAlgorithm()); + JwsHeader.Builder builder = JwsHeader.with(algorithm).type(jwk.getKeyType().getValue()).keyId(jwk.getKeyID()); + URI x509Url = jwk.getX509CertURL(); + if (x509Url != null) { + builder.x509Url(jwk.getX509CertURL().toASCIIString()); + } + List certs = jwk.getX509CertChain(); + if (certs != null) { + builder.x509CertificateChain(certs.stream().map(Base64::toString).toList()); + } + Base64URL thumbprint = jwk.getX509CertSHA256Thumbprint(); + if (thumbprint != null) { + builder.x509SHA256Thumbprint(thumbprint.toString()); + } + this.defaultJwsHeader = builder.build(); + } + /** * Use this strategy to reduce the list of matching JWKs when there is more than one. *

@@ -133,16 +167,15 @@ public final class NimbusJwtEncoder implements JwtEncoder { this.jwkSelector = jwkSelector; } - public void setJwsHeader(JwsHeader jwsHeader) { - this.jwsHeader = jwsHeader; - } - @Override public Jwt encode(JwtEncoderParameters parameters) throws JwtEncodingException { Assert.notNull(parameters, "parameters cannot be null"); JwsHeader headers = parameters.getJwsHeader(); - headers = (headers != null) ? headers : (this.jwsHeader != null) ? this.jwsHeader : DEFAULT_JWS_HEADER; + if (headers == null) { + headers = this.defaultJwsHeader; + } + JwtClaimsSet claims = parameters.getClaims(); JWK jwk = selectJwk(headers); @@ -387,38 +420,34 @@ public final class NimbusJwtEncoder implements JwtEncoder { /** * Creates a builder for constructing a {@link NimbusJwtEncoder} using the provided - * {@link SecretKey}. - * @param secretKey the {@link SecretKey} to use for signing JWTs - * @return a {@link SecretKeyJwtEncoderBuilder} for further configuration + * @param publicKey the {@link RSAPublicKey} and @Param privateKey the + * {@link RSAPrivateKey} to use for signing JWTs + * @return a {@link RsaKeyPairJwtEncoderBuilder} * @since 7.0 */ - public static SecretKeyJwtEncoderBuilder withSecretKey(SecretKey secretKey) { - Assert.notNull(secretKey, "secretKey cannot be null"); - return new SecretKeyJwtEncoderBuilder(secretKey); + public static RsaKeyPairJwtEncoderBuilder withKeyPair(RSAPublicKey publicKey, RSAPrivateKey privateKey) { + return new RsaKeyPairJwtEncoderBuilder(publicKey, privateKey); } /** * Creates a builder for constructing a {@link NimbusJwtEncoder} using the provided - * {@link KeyPair}. The key pair must contain either an {@link RSAKey} or an - * {@link ECKey}. - * @param keyPair the {@link KeyPair} to use for signing JWTs - * @return a {@link KeyPairJwtEncoderBuilder} for further configuration + * @param publicKey the {@link ECPublicKey} and @param privateKey the + * {@link ECPrivateKey} to use for signing JWTs + * @return a {@link EcKeyPairJwtEncoderBuilder} * @since 7.0 */ - public static KeyPairJwtEncoderBuilder withKeyPair(KeyPair keyPair) { - Assert.isTrue(keyPair != null && keyPair.getPrivate() != null && keyPair.getPublic() != null, - "keyPair, its private key, and public key must not be null"); - Assert.isTrue( - keyPair.getPrivate() instanceof java.security.interfaces.RSAKey - || keyPair.getPrivate() instanceof java.security.interfaces.ECKey, - "keyPair must be an RSAKey or an ECKey"); - if (keyPair.getPrivate() instanceof java.security.interfaces.RSAKey) { - return new RsaKeyPairJwtEncoderBuilder(keyPair); - } - if (keyPair.getPrivate() instanceof java.security.interfaces.ECKey) { - return new EcKeyPairJwtEncoderBuilder(keyPair); - } - throw new IllegalArgumentException("keyPair must be an RSAKey or an ECKey"); + public static EcKeyPairJwtEncoderBuilder withKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) { + return new EcKeyPairJwtEncoderBuilder(publicKey, privateKey); + } + + /** + * Creates a builder for constructing a {@link NimbusJwtEncoder} using the provided + * @param secretKey + * @return a {@link SecretKeyJwtEncoderBuilder} for configuring the {@link JWK} + * @since 7.0 + */ + public static SecretKeyJwtEncoderBuilder withSecretKey(SecretKey secretKey) { + return new SecretKeyJwtEncoderBuilder(secretKey); } /** @@ -429,14 +458,29 @@ public final class NimbusJwtEncoder implements JwtEncoder { */ public static final class SecretKeyJwtEncoderBuilder { - private final SecretKey secretKey; + private static final ThrowingFunction defaultJwk = JWKS::signing; - private String keyId; + private final OctetSequenceKey.Builder builder; - private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256; + private final Set allowedAlgorithms; private SecretKeyJwtEncoderBuilder(SecretKey secretKey) { - this.secretKey = secretKey; + Assert.notNull(secretKey, "secretKey cannot be null"); + Set allowedAlgorithms = computeAllowedAlgorithms(secretKey); + Assert.notEmpty(allowedAlgorithms, + "This key is too small for any standard JWK symmetric signing algorithm"); + this.allowedAlgorithms = allowedAlgorithms; + this.builder = defaultJwk.apply(secretKey, IllegalArgumentException::new) + .algorithm(this.allowedAlgorithms.iterator().next()); + } + + private Set computeAllowedAlgorithms(SecretKey secretKey) { + try { + return new MACSigner(secretKey).supportedJWSAlgorithms(); + } + catch (JOSEException ex) { + throw new IllegalArgumentException(ex); + } } /** @@ -446,24 +490,24 @@ public final class NimbusJwtEncoder implements JwtEncoder { * @param macAlgorithm the {@link MacAlgorithm} to use * @return this builder instance for method chaining */ - public SecretKeyJwtEncoderBuilder macAlgorithm(MacAlgorithm macAlgorithm) { + public SecretKeyJwtEncoderBuilder algorithm(MacAlgorithm macAlgorithm) { Assert.notNull(macAlgorithm, "macAlgorithm cannot be null"); - Assert.state(JWSAlgorithm.Family.HMAC_SHA.contains(this.jwsAlgorithm), - () -> "The algorithm '" + this.jwsAlgorithm + "' is not compatible with a SecretKey. " - + "Please use one of the HS256, HS384, or HS512 algorithms."); - - this.jwsAlgorithm = JWSAlgorithm.parse(macAlgorithm.getName()); + JWSAlgorithm jws = JWSAlgorithm.parse(macAlgorithm.getName()); + Assert.isTrue(this.allowedAlgorithms.contains(jws), String + .format("This key can only support " + "the following algorithms: [%s]", this.allowedAlgorithms)); + this.builder.algorithm(JWSAlgorithm.parse(macAlgorithm.getName())); return this; } /** - * Sets the key ID ({@code kid}) to be included in the JWK and potentially the JWS - * header. - * @param keyId the key identifier + * Post-process the {@link JWK} using the given {@link Consumer}. For example, you + * may use this to override the default {@code kid} + * @param jwkPostProcessor the post-processor to use * @return this builder instance for method chaining */ - public SecretKeyJwtEncoderBuilder keyId(String keyId) { - this.keyId = keyId; + public SecretKeyJwtEncoderBuilder jwkPostProcessor(Consumer jwkPostProcessor) { + Assert.notNull(jwkPostProcessor, "jwkPostProcessor cannot be null"); + jwkPostProcessor.accept(this.builder); return this; } @@ -474,17 +518,7 @@ public final class NimbusJwtEncoder implements JwtEncoder { * with a {@link SecretKey}. */ public NimbusJwtEncoder build() { - this.jwsAlgorithm = (this.jwsAlgorithm != null) ? this.jwsAlgorithm : JWSAlgorithm.HS256; - - OctetSequenceKey.Builder builder = new OctetSequenceKey.Builder(this.secretKey).keyUse(KeyUse.SIGNATURE) - .algorithm(this.jwsAlgorithm) - .keyID(this.keyId); - - OctetSequenceKey jwk = builder.build(); - JWKSource jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk)); - NimbusJwtEncoder encoder = new NimbusJwtEncoder(jwkSource); - encoder.setJwsHeader(JwsHeader.with(MacAlgorithm.from(this.jwsAlgorithm.getName())).build()); - return encoder; + return new NimbusJwtEncoder(this.builder.build()); } } @@ -495,137 +529,93 @@ public final class NimbusJwtEncoder implements JwtEncoder { * * @since 7.0 */ - public abstract static class KeyPairJwtEncoderBuilder { + public static final class RsaKeyPairJwtEncoderBuilder { - private final KeyPair keyPair; + private static final ThrowingBiFunction defaultKid = JWKS::signingWithRsa; - private String keyId; + private final RSAKey.Builder builder; - private JWSAlgorithm jwsAlgorithm; - - private KeyPairJwtEncoderBuilder(KeyPair keyPair) { - this.keyPair = keyPair; + private RsaKeyPairJwtEncoderBuilder(RSAPublicKey publicKey, RSAPrivateKey privateKey) { + Assert.notNull(publicKey, "publicKey cannot be null"); + Assert.notNull(privateKey, "privateKey cannot be null"); + this.builder = defaultKid.apply(publicKey, privateKey); } /** - * Sets the JWS algorithm to use for signing. Must be compatible with the key type - * (RSA or EC). If not set, a default algorithm will be chosen based on the key - * type (e.g., RS256 for RSA, ES256 for EC). + * Sets the JWS algorithm to use for signing. Defaults to + * {@link SignatureAlgorithm#RS256}. Must be an RSA-based algorithm * @param signatureAlgorithm the {@link SignatureAlgorithm} to use * @return this builder instance for method chaining */ - public KeyPairJwtEncoderBuilder signatureAlgorithm(SignatureAlgorithm signatureAlgorithm) { + public RsaKeyPairJwtEncoderBuilder algorithm(SignatureAlgorithm signatureAlgorithm) { Assert.notNull(signatureAlgorithm, "signatureAlgorithm cannot be null"); - this.jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName()); + this.builder.algorithm(JWSAlgorithm.parse(signatureAlgorithm.getName())); return this; } /** - * Sets the key ID ({@code kid}) to be included in the JWK and potentially the JWS - * header. - * @param keyId the key identifier + * Add commentMore actions Post-process the {@link JWK} using the given + * {@link Consumer}. For example, you may use this to override the default + * {@code kid} + * @param jwkPostProcessor the post-processor to use * @return this builder instance for method chaining */ - public KeyPairJwtEncoderBuilder keyId(String keyId) { - this.keyId = keyId; + public RsaKeyPairJwtEncoderBuilder jwkPostProcessor(Consumer jwkPostProcessor) { + Assert.notNull(jwkPostProcessor, "jwkPostProcessor cannot be null"); + jwkPostProcessor.accept(this.builder); return this; } /** * Builds the {@link NimbusJwtEncoder} instance. * @return the configured {@link NimbusJwtEncoder} - * @throws IllegalStateException if the key type is unsupported or the configured - * JWS algorithm is not compatible with the key type. - * @throws JwtEncodingException if the key is invalid (e.g., EC key with unknown - * curve) */ public NimbusJwtEncoder build() { - this.keyId = (this.keyId != null) ? this.keyId : UUID.randomUUID().toString(); - JWK jwk = buildJwk(); - JWKSource jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk)); - NimbusJwtEncoder encoder = new NimbusJwtEncoder(jwkSource); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.from(this.jwsAlgorithm.getName())) - .keyId(jwk.getKeyID()) - .build(); - encoder.setJwsHeader(jwsHeader); - return encoder; - } - - protected abstract JWK buildJwk(); - - } - - /** - * A builder for creating {@link NimbusJwtEncoder} instances configured with a - * {@link KeyPair}. - * - * @since 7.0 - */ - public static final class RsaKeyPairJwtEncoderBuilder extends KeyPairJwtEncoderBuilder { - - private RsaKeyPairJwtEncoderBuilder(KeyPair keyPair) { - super(keyPair); - } - - @Override - protected JWK buildJwk() { - if (super.jwsAlgorithm == null) { - super.jwsAlgorithm = JWSAlgorithm.RS256; - } - Assert.state(JWSAlgorithm.Family.RSA.contains(super.jwsAlgorithm), - () -> "The algorithm '" + super.jwsAlgorithm + "' is not compatible with an RSAKey. " - + "Please use one of the RS256, RS384, RS512, PS256, PS384, or PS512 algorithms."); - - RSAKey.Builder builder = new RSAKey.Builder( - (java.security.interfaces.RSAPublicKey) super.keyPair.getPublic()) - .privateKey(super.keyPair.getPrivate()) - .keyID(super.keyId) - .keyUse(KeyUse.SIGNATURE) - .algorithm(super.jwsAlgorithm); - return builder.build(); + return new NimbusJwtEncoder(this.builder.build()); } } /** * A builder for creating {@link NimbusJwtEncoder} instances configured with a - * {@link KeyPair}. + * {@link ECPublicKey} and {@link ECPrivateKey}. + *

+ * This builder is used to create a {@link NimbusJwtEncoder} * * @since 7.0 */ - public static final class EcKeyPairJwtEncoderBuilder extends KeyPairJwtEncoderBuilder { + public static final class EcKeyPairJwtEncoderBuilder { - private EcKeyPairJwtEncoderBuilder(KeyPair keyPair) { - super(keyPair); - } + private static final ThrowingBiFunction defaultKid = JWKS::signingWithEc; - @Override - protected JWK buildJwk() { - if (super.jwsAlgorithm == null) { - super.jwsAlgorithm = JWSAlgorithm.ES256; - } - Assert.state(JWSAlgorithm.Family.EC.contains(super.jwsAlgorithm), - () -> "The algorithm '" + super.jwsAlgorithm + "' is not compatible with an ECKey. " - + "Please use one of the ES256, ES384, or ES512 algorithms."); + private final ECKey.Builder builder; - ECPublicKey publicKey = (ECPublicKey) super.keyPair.getPublic(); + private EcKeyPairJwtEncoderBuilder(ECPublicKey publicKey, ECPrivateKey privateKey) { + Assert.notNull(publicKey, "publicKey cannot be null"); + Assert.notNull(privateKey, "privateKey cannot be null"); Curve curve = Curve.forECParameterSpec(publicKey.getParams()); - if (curve == null) { - throw new JwtEncodingException("Unable to determine Curve for EC public key."); - } + Assert.notNull(curve, "Unable to determine Curve for EC public key."); + this.builder = defaultKid.apply(publicKey, privateKey); + } - com.nimbusds.jose.jwk.ECKey.Builder builder = new com.nimbusds.jose.jwk.ECKey.Builder(curve, publicKey) - .privateKey(super.keyPair.getPrivate()) - .keyUse(KeyUse.SIGNATURE) - .keyID(super.keyId) - .algorithm(super.jwsAlgorithm); + /** + * Post-process the {@link JWK} using the given {@link Consumer}. For example, you + * may use this to override the default {@code kid} + * @param jwkPostProcessor the post-processor to use + * @return this builder instance for method chaining + */ + public EcKeyPairJwtEncoderBuilder jwkPostProcessor(Consumer jwkPostProcessor) { + Assert.notNull(jwkPostProcessor, "jwkPostProcessor cannot be null"); + jwkPostProcessor.accept(this.builder); + return this; + } - try { - return builder.build(); - } - catch (IllegalStateException ex) { - throw new IllegalArgumentException("Failed to build ECKey: " + ex.getMessage(), ex); - } + /** + * Builds the {@link NimbusJwtEncoder} instance. + * @return the configured {@link NimbusJwtEncoder} + */ + public NimbusJwtEncoder build() { + return new NimbusJwtEncoder(this.builder.build()); } } diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java index 2abbaebf33..840b9cdcca 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java @@ -16,8 +16,6 @@ package org.springframework.security.oauth2.jwt; -import java.security.KeyPair; -import java.security.KeyPairGenerator; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.time.Instant; @@ -27,12 +25,15 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.function.Consumer; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.KeySourceException; +import com.nimbusds.jose.jwk.Curve; import com.nimbusds.jose.jwk.ECKey; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSelector; @@ -40,6 +41,8 @@ import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.OctetSequenceKey; import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.gen.ECKeyGenerator; +import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.util.Base64URL; @@ -51,12 +54,12 @@ import org.mockito.stubbing.Answer; import org.springframework.core.convert.converter.Converter; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jose.TestKeys; +import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; 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; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; @@ -353,160 +356,138 @@ public class NimbusJwtEncoderTests { verifyNoInteractions(selector); } + // Default algorithm @Test - void secretKeyBuilderWithDefaultAlgorithm() { - SecretKey secretKey = new SecretKeySpec("thisIsASecretKeyUsedForTesting12345".getBytes(), "HMAC"); + void keyPairBuilderWithRsaDefaultAlgorithm() throws JOSEException { + RSAKeyGenerator generator = new RSAKeyGenerator(2048); + RSAKey key = generator.generate(); + NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(key.toRSAPublicKey(), key.toRSAPrivateKey()).build(); JwtClaimsSet claims = buildClaims(); - - NimbusJwtEncoder encoder = NimbusJwtEncoder.withSecretKey(secretKey).build(); - Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); - - assertThat(jwt).isNotNull(); - assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("HS256"); - assertThatNoException().isThrownBy(jwt::getClaims); + Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)); assertJwt(jwt); + assertThat(jwt.getHeaders()).containsKey(JoseHeaderNames.KID); } @Test - void secretKeyBuilderWithKeyId() { - SecretKey secretKey = new SecretKeySpec("thisIsASecretKeyUsedForTesting12345".getBytes(), "HMAC"); - String keyId = "test-key-id"; + void keyPairBuilderWithEcDefaultAlgorithm() throws JOSEException { + ECKeyGenerator generator = new ECKeyGenerator(Curve.P_256); + ECKey key = generator.generate(); + NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(key.toECPublicKey(), key.toECPrivateKey()).build(); JwtClaimsSet claims = buildClaims(); - - NimbusJwtEncoder encoder = NimbusJwtEncoder.withSecretKey(secretKey).keyId(keyId).build(); - Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); - - assertThat(jwt).isNotNull(); - assertThat(jwt.getHeaders().get("kid").toString()).isEqualTo(keyId); - assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("HS256"); - assertThatNoException().isThrownBy(jwt::getClaims); + Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)); assertJwt(jwt); + assertThat(jwt.getHeaders()).containsKey(JoseHeaderNames.KID); } @Test - void secretKeyBuilderWithCustomJwkSelector() { - SecretKey secretKey = new SecretKeySpec("thisIsASecretKeyUsedForTesting12345".getBytes(), "HMAC"); - String keyId = "test-key-id"; + void keyPairBuilderWithSecretKeyDefaultAlgorithm() { + SecretKey key = TestKeys.DEFAULT_SECRET_KEY; + NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withSecretKey(key).build(); JwtClaimsSet claims = buildClaims(); - - NimbusJwtEncoder encoder = NimbusJwtEncoder.withSecretKey(secretKey).keyId(keyId).build(); - Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); - - assertThat(jwt).isNotNull(); - assertThat(jwt.getHeaders().get("kid")).isEqualTo(keyId); - assertThat(jwt.getClaims()).containsEntry("sub", "subject"); - assertThatNoException().isThrownBy(() -> jwt.getClaims()); + Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)); assertJwt(jwt); + assertThat(jwt.getHeaders()).containsKey(JoseHeaderNames.KID); } + // With custom algorithm @Test - void secretKeyBuilderWithCustomHeaders() { - SecretKey secretKey = new SecretKeySpec("thisIsASecretKeyUsedForTesting12345".getBytes(), "HMAC"); - JwtClaimsSet claims = buildClaims(); - JwsHeader headers = JwsHeader.with(org.springframework.security.oauth2.jose.jws.MacAlgorithm.HS256) - .type("JWT") - .contentType("application/jwt") + void keyPairBuilderWithRsaWithAlgorithm() throws JOSEException { + RSAKeyGenerator generator = new RSAKeyGenerator(2048); + RSAKey key = generator.generate(); + NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(key.toRSAPublicKey(), key.toRSAPrivateKey()) + .algorithm(SignatureAlgorithm.RS384) .build(); - - NimbusJwtEncoder encoder = NimbusJwtEncoder.withSecretKey(secretKey).build(); - Jwt jwt = encoder.encode(JwtEncoderParameters.from(headers, claims)); - - assertThat(jwt).isNotNull(); - assertThat(jwt.getHeaders().get("typ").toString()).isEqualTo("JWT"); - assertThat(jwt.getHeaders().get("cty").toString()).isEqualTo("application/jwt"); - assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("HS256"); - assertThatNoException().isThrownBy(() -> jwt.getClaims()); + JwtClaimsSet claims = buildClaims(); + Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)); assertJwt(jwt); + assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.ALG, SignatureAlgorithm.RS384); + assertThat(jwt.getHeaders()).containsKey(JoseHeaderNames.KID); } @Test - void keyPairBuilderWithRsaDefaultAlgorithm() throws Exception { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); - KeyPair keyPair = keyPairGenerator.generateKeyPair(); + void keyPairBuilderWithEcWithAlgorithm() throws JOSEException { + ECKeyGenerator generator = new ECKeyGenerator(Curve.P_384); + ECKey key = generator.generate(); + NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(key.toECPublicKey(), key.toECPrivateKey()).build(); JwtClaimsSet claims = buildClaims(); - - NimbusJwtEncoder encoder = NimbusJwtEncoder.withKeyPair(keyPair).build(); - Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); - - assertThat(jwt).isNotNull(); - assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("RS256"); - assertThat(jwt.getSubject()).isEqualTo(claims.getSubject()); - assertThat(jwt.getAudience()).isEqualTo(claims.getAudience()); - assertThatNoException().isThrownBy(() -> jwt.getClaims()); + Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)); assertJwt(jwt); + assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.ALG, SignatureAlgorithm.ES384); + assertThat(jwt.getHeaders()).containsKey(JoseHeaderNames.KID); } @Test - void keyPairBuilderWithRsaCustomAlgorithm() throws Exception { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); - KeyPair keyPair = keyPairGenerator.generateKeyPair(); + void keyPairBuilderWithSecretKeyWithAlgorithm() { + String keyStr = UUID.randomUUID().toString(); + keyStr += keyStr; + SecretKey Key = new SecretKeySpec(keyStr.getBytes(), "AES"); + NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withSecretKey(Key).algorithm(MacAlgorithm.HS512).build(); JwtClaimsSet claims = buildClaims(); + Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)); + assertJwt(jwt); + assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.ALG, MacAlgorithm.HS512); + assertThat(jwt.getHeaders()).containsKey(JoseHeaderNames.KID); + } - NimbusJwtEncoder encoder = NimbusJwtEncoder.withKeyPair(keyPair) - .signatureAlgorithm(SignatureAlgorithm.RS512) + @Test + void keyPairBuilderWhenShortSecretThenHigherAlgorithmNotSupported() { + String keyStr = UUID.randomUUID().toString(); + SecretKey Key = new SecretKeySpec(keyStr.getBytes(), "AES"); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> NimbusJwtEncoder.withSecretKey(Key).algorithm(MacAlgorithm.HS512).build()); + } + + @Test + void keyPairBuilderWhenTooShortSecretThenException() { + SecretKey Key = new SecretKeySpec("key".getBytes(), "AES"); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> NimbusJwtEncoder.withSecretKey(Key)); + } + + // with custom jwkPostProcessor + @Test + void keyPairBuilderWithRsaWithAlgorithmAndJwkSource() throws JOSEException { + RSAKeyGenerator generator = new RSAKeyGenerator(2048); + RSAKey key = generator.generate(); + String keyId = UUID.randomUUID().toString(); + NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(key.toRSAPublicKey(), key.toRSAPrivateKey()) + .algorithm(SignatureAlgorithm.RS384) + .jwkPostProcessor((builder) -> builder.keyID(keyId)) .build(); - Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); - - assertThat(jwt).isNotNull(); - assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("RS512"); - assertThat(jwt.getSubject()).isEqualTo(claims.getSubject()); - assertThatNoException().isThrownBy(() -> jwt.getClaims()); - assertJwt(jwt); - } - - @Test - void keyPairBuilderWithEcDefaultAlgorithm() throws Exception { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); - keyPairGenerator.initialize(256); - KeyPair keyPair = keyPairGenerator.generateKeyPair(); JwtClaimsSet claims = buildClaims(); - - NimbusJwtEncoder encoder = NimbusJwtEncoder.withKeyPair(keyPair).build(); - Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); - - assertThat(jwt).isNotNull(); - assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("ES256"); - assertThat(jwt.getSubject()).isEqualTo(claims.getSubject()); - assertThatNoException().isThrownBy(() -> jwt.getClaims()); + Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)); assertJwt(jwt); + assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.ALG, SignatureAlgorithm.RS384); + assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.KID, keyId); } @Test - void keyPairBuilderWithEcCustomAlgorithm() throws Exception { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); - keyPairGenerator.initialize(256); - KeyPair keyPair = keyPairGenerator.generateKeyPair(); - NimbusJwtEncoder encoder = NimbusJwtEncoder.withKeyPair(keyPair) - .keyId(UUID.randomUUID().toString()) - .signatureAlgorithm(SignatureAlgorithm.ES256) + void keyPairBuilderWithEcWithAlgorithmAndJwkSource() throws JOSEException { + ECKeyGenerator generator = new ECKeyGenerator(Curve.P_256); + ECKey key = generator.generate(); + String keyId = UUID.randomUUID().toString(); + Consumer jwkPostProcessor = (builder) -> builder.keyID(keyId); + NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(key.toECPublicKey(), key.toECPrivateKey()) + .jwkPostProcessor(jwkPostProcessor) .build(); - JwtClaimsSet claims = buildClaims(); - Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); - - assertThat(jwt).isNotNull(); - assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("ES256"); - assertThatNoException().isThrownBy(() -> jwt.getClaims()); + Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)); assertJwt(jwt); + assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.ALG, SignatureAlgorithm.ES256); + assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.KID, keyId); } @Test - void keyPairBuilderWithKeyId() throws Exception { // d - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); - KeyPair keyPair = keyPairGenerator.generateKeyPair(); - String keyId = "test-key-id"; + void keyPairBuilderWithSecretKeyWithAlgorithmAndJwkSource() { + final String keyStr = UUID.randomUUID().toString(); + SecretKey key = new SecretKeySpec(keyStr.getBytes(), "HS256"); + String keyId = UUID.randomUUID().toString(); + Consumer jwkPostProcessor = (builder) -> builder.keyID(keyId); + NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withSecretKey(key).jwkPostProcessor(jwkPostProcessor).build(); JwtClaimsSet claims = buildClaims(); - - NimbusJwtEncoder encoder = NimbusJwtEncoder.withKeyPair(keyPair).keyId(keyId).build(); - Jwt jwt = encoder.encode(JwtEncoderParameters.from(claims)); - - assertThat(jwt).isNotNull(); - assertThat(jwt.getHeaders().get("kid")).isEqualTo(keyId); - assertThat(jwt.getHeaders().get("alg").toString()).isEqualTo("RS256"); - assertThatNoException().isThrownBy(() -> jwt.getClaims()); + Jwt jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)); + assertJwt(jwt); + assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.ALG, MacAlgorithm.HS256); + assertThat(jwt.getHeaders()).containsEntry(JoseHeaderNames.KID, keyId); } private JwtClaimsSet buildClaims() { From 06ed6ef342a114e8710b629c9123d857a00b4890 Mon Sep 17 00:00:00 2001 From: evga7 Date: Sun, 15 Jun 2025 05:04:38 +0900 Subject: [PATCH 146/149] Simplify Csrf Processor Decision Logic Replaces repeated if-else string comparisons with a Set.contains() check for known WebSocket handshake handler class names in MessageSecurityPostProcessor. Improves readability and maintainability without changing behavior. Signed-off-by: Wonpyo Hong --- ...ageBrokerSecurityBeanDefinitionParser.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java index edb6646b36..9f4dc88f15 100644 --- a/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java @@ -19,6 +19,11 @@ package org.springframework.security.config.websocket; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.HashSet; +import java.util.Arrays; +import java.util.Collections; + import java.util.function.Supplier; import org.w3c.dom.Element; @@ -307,6 +312,13 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements private static final String TEMPLATE_EXPRESSION_BEAN_ID = "annotationExpressionTemplateDefaults"; + private static final Set CSRF_HANDSHAKE_HANDLER_CLASSES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList( + "org.springframework.web.socket.server.support.WebSocketHttpRequestHandler", + "org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService", + "org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService" + ))); + private final String inboundSecurityInterceptorId; private final boolean sameOriginDisabled; @@ -345,16 +357,7 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements } } } - else if ("org.springframework.web.socket.server.support.WebSocketHttpRequestHandler" - .equals(beanClassName)) { - addCsrfTokenHandshakeInterceptor(bd); - } - else if ("org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService" - .equals(beanClassName)) { - addCsrfTokenHandshakeInterceptor(bd); - } - else if ("org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService" - .equals(beanClassName)) { + else if (CSRF_HANDSHAKE_HANDLER_CLASSES.contains(beanClassName)) { addCsrfTokenHandshakeInterceptor(bd); } } From 42e24aa53c3ca587c1a398183e7762cae5df66c0 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 17 Jun 2025 16:39:05 -0600 Subject: [PATCH 147/149] Fix Formatting --- ...etMessageBrokerSecurityBeanDefinitionParser.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java index 9f4dc88f15..2c903ce527 100644 --- a/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java @@ -16,14 +16,13 @@ package org.springframework.security.config.websocket; +import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.HashSet; -import java.util.Arrays; -import java.util.Collections; - import java.util.function.Supplier; import org.w3c.dom.Element; @@ -313,11 +312,9 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements private static final String TEMPLATE_EXPRESSION_BEAN_ID = "annotationExpressionTemplateDefaults"; private static final Set CSRF_HANDSHAKE_HANDLER_CLASSES = Collections.unmodifiableSet( - new HashSet<>(Arrays.asList( - "org.springframework.web.socket.server.support.WebSocketHttpRequestHandler", + new HashSet<>(Arrays.asList("org.springframework.web.socket.server.support.WebSocketHttpRequestHandler", "org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService", - "org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService" - ))); + "org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService"))); private final String inboundSecurityInterceptorId; From 6ddb964c619ed1c9eeead3f6b2c86718ee320434 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 18 Jun 2025 18:29:16 -0600 Subject: [PATCH 148/149] Remove ApacheDS Support Closes gh-13852 --- config/spring-security-config.gradle | 6 - ...onProviderBuilderSecurityBuilderTests.java | 10 +- ...indAuthenticationManagerFactoryITests.java | 12 +- ...sonAuthenticationManagerFactoryITests.java | 12 +- ...LdapProviderBeanDefinitionParserTests.java | 23 +- .../LdapServerBeanDefinitionParserTests.java | 6 +- .../resources/logback-test.xml | 1 - .../security/config/BeanIds.java | 2 - .../LdapAuthenticationProviderConfigurer.java | 18 +- .../ldap/LdapServerBeanDefinitionParser.java | 30 +- .../security/config/spring-security-7.0.rnc | 4 +- .../security/config/spring-security-7.0.xsd | 3 +- ...ntextConfigurationResourceServerTests.java | 4 +- .../config/doc/XsdDocumentedTests.java | 6 +- config/src/test/resources/logback-test.xml | 2 - .../spring-security-dependencies.gradle | 6 - docs/modules/ROOT/pages/modules.adoc | 10 +- .../ROOT/pages/servlet/appendix/faq.adoc | 53 +-- .../servlet/appendix/namespace/ldap.adoc | 2 +- .../authentication/passwords/ldap.adoc | 78 +--- .../servlet/configuration/xml-namespace.adoc | 2 +- docs/spring-security-docs.gradle | 1 - gradle/libs.versions.toml | 7 - ...test-ldap-embedded-apacheds-default.gradle | 30 -- .../LdapServerBeanDefinitionParserTests.java | 56 --- .../resources/applicationContext-security.xml | 9 - .../src/integration-test/resources/users.ldif | 60 --- ...y-itest-ldap-embedded-mode-apacheds.gradle | 30 -- .../LdapServerBeanDefinitionParserTests.java | 56 --- .../resources/applicationContext-security.xml | 9 - .../src/integration-test/resources/users.ldif | 60 --- ldap/spring-security-ldap.gradle | 6 - ...faultSpringSecurityContextSourceTests.java | 2 +- .../SpringSecurityLdapTemplateITests.java | 12 +- ...fig.java => UnboundIdContainerConfig.java} | 16 +- .../BindAuthenticatorTests.java | 4 +- .../PasswordComparisonAuthenticatorTests.java | 4 +- .../FilterBasedLdapUserSearchTests.java | 4 +- ...terBasedLdapUserSearchWithSpacesTests.java | 16 +- .../ldap/server/ApacheDSContainerTests.java | 221 ----------- .../server/ApacheDSEmbeddedLdifTests.java | 79 ---- ...esPopulatorGetGrantedAuthoritiesTests.java | 16 +- .../DefaultLdapAuthoritiesPopulatorTests.java | 4 +- .../LdapUserDetailsManagerTests.java | 4 +- .../NestedLdapAuthoritiesPopulatorTests.java | 4 +- .../ldap/server/ApacheDSContainer.java | 365 ------------------ .../security/ldap/server/package-info.java | 3 +- .../server/core/avltree/ArrayMarshaller.java | 173 --------- ...ectoryLdapAuthenticationProviderTests.java | 3 +- 49 files changed, 138 insertions(+), 1406 deletions(-) delete mode 100644 itest/ldap/embedded-ldap-apacheds-default/spring-security-itest-ldap-embedded-apacheds-default.gradle delete mode 100644 itest/ldap/embedded-ldap-apacheds-default/src/integration-test/java/org/springframework/security/LdapServerBeanDefinitionParserTests.java delete mode 100644 itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/applicationContext-security.xml delete mode 100644 itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/users.ldif delete mode 100644 itest/ldap/embedded-ldap-mode-apacheds/spring-security-itest-ldap-embedded-mode-apacheds.gradle delete mode 100644 itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/java/org/springframework/security/LdapServerBeanDefinitionParserTests.java delete mode 100644 itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/applicationContext-security.xml delete mode 100644 itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/users.ldif rename ldap/src/integration-test/java/org/springframework/security/ldap/{ApacheDsContainerConfig.java => UnboundIdContainerConfig.java} (67%) delete mode 100644 ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java delete mode 100644 ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSEmbeddedLdifTests.java delete mode 100644 ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java delete mode 100644 ldap/src/test/java/org/apache/directory/server/core/avltree/ArrayMarshaller.java diff --git a/config/spring-security-config.gradle b/config/spring-security-config.gradle index 83f91c9bf0..322f77af47 100644 --- a/config/spring-security-config.gradle +++ b/config/spring-security-config.gradle @@ -78,12 +78,6 @@ dependencies { exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'xml-apis', module: 'xml-apis' } - testImplementation "org.apache.directory.server:apacheds-core" - testImplementation "org.apache.directory.server:apacheds-core-entry" - testImplementation "org.apache.directory.server:apacheds-protocol-shared" - testImplementation "org.apache.directory.server:apacheds-protocol-ldap" - testImplementation "org.apache.directory.server:apacheds-server-jndi" - testImplementation 'org.apache.directory.shared:shared-ldap' testImplementation "com.unboundid:unboundid-ldapsdk" testImplementation 'jakarta.persistence:jakarta.persistence-api' testImplementation "org.hibernate.orm:hibernate-core" diff --git a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java index 17362f02ae..890ed32048 100644 --- a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java +++ b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java @@ -44,7 +44,7 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMap import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.authentication.LdapAuthenticationProvider; -import org.springframework.security.ldap.server.ApacheDSContainer; +import org.springframework.security.ldap.server.UnboundIdContainer; import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; @@ -326,11 +326,11 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests { abstract static class BaseLdapServerConfig extends BaseLdapProviderConfig { @Bean - ApacheDSContainer ldapServer() throws Exception { - ApacheDSContainer apacheDSContainer = new ApacheDSContainer("dc=springframework,dc=org", + UnboundIdContainer ldapServer() throws Exception { + UnboundIdContainer unboundIdContainer = new UnboundIdContainer("dc=springframework,dc=org", "classpath:/test-server.ldif"); - apacheDSContainer.setPort(getPort()); - return apacheDSContainer; + unboundIdContainer.setPort(getPort()); + return unboundIdContainer; } } diff --git a/config/src/integration-test/java/org/springframework/security/config/ldap/LdapBindAuthenticationManagerFactoryITests.java b/config/src/integration-test/java/org/springframework/security/config/ldap/LdapBindAuthenticationManagerFactoryITests.java index 9496ba2651..25c02eb69a 100644 --- a/config/src/integration-test/java/org/springframework/security/config/ldap/LdapBindAuthenticationManagerFactoryITests.java +++ b/config/src/integration-test/java/org/springframework/security/config/ldap/LdapBindAuthenticationManagerFactoryITests.java @@ -43,7 +43,7 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMap import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; -import org.springframework.security.ldap.server.ApacheDSContainer; +import org.springframework.security.ldap.server.UnboundIdContainer; import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.UserDetailsContextMapper; @@ -226,18 +226,18 @@ public class LdapBindAuthenticationManagerFactoryITests { @EnableWebSecurity abstract static class BaseLdapServerConfig implements DisposableBean { - private ApacheDSContainer container; + private UnboundIdContainer container; @Bean - ApacheDSContainer ldapServer() throws Exception { - this.container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/test-server.ldif"); + UnboundIdContainer ldapServer() { + this.container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:/test-server.ldif"); this.container.setPort(0); return this.container; } @Bean - BaseLdapPathContextSource contextSource(ApacheDSContainer container) { - int port = container.getLocalPort(); + BaseLdapPathContextSource contextSource(UnboundIdContainer container) { + int port = container.getPort(); return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org"); } diff --git a/config/src/integration-test/java/org/springframework/security/config/ldap/LdapPasswordComparisonAuthenticationManagerFactoryITests.java b/config/src/integration-test/java/org/springframework/security/config/ldap/LdapPasswordComparisonAuthenticationManagerFactoryITests.java index 668fe9b994..fd0ace02aa 100644 --- a/config/src/integration-test/java/org/springframework/security/config/ldap/LdapPasswordComparisonAuthenticationManagerFactoryITests.java +++ b/config/src/integration-test/java/org/springframework/security/config/ldap/LdapPasswordComparisonAuthenticationManagerFactoryITests.java @@ -31,7 +31,7 @@ import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; -import org.springframework.security.ldap.server.ApacheDSContainer; +import org.springframework.security.ldap.server.UnboundIdContainer; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; @@ -93,18 +93,18 @@ public class LdapPasswordComparisonAuthenticationManagerFactoryITests { @EnableWebSecurity abstract static class BaseLdapServerConfig implements DisposableBean { - private ApacheDSContainer container; + private UnboundIdContainer container; @Bean - ApacheDSContainer ldapServer() throws Exception { - this.container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/test-server.ldif"); + UnboundIdContainer ldapServer() { + this.container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:/test-server.ldif"); this.container.setPort(0); return this.container; } @Bean - BaseLdapPathContextSource contextSource(ApacheDSContainer container) { - int port = container.getLocalPort(); + BaseLdapPathContextSource contextSource(UnboundIdContainer container) { + int port = container.getPort(); return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org"); } diff --git a/config/src/integration-test/java/org/springframework/security/config/ldap/LdapProviderBeanDefinitionParserTests.java b/config/src/integration-test/java/org/springframework/security/config/ldap/LdapProviderBeanDefinitionParserTests.java index 162fcc8495..a992de947e 100644 --- a/config/src/integration-test/java/org/springframework/security/config/ldap/LdapProviderBeanDefinitionParserTests.java +++ b/config/src/integration-test/java/org/springframework/security/config/ldap/LdapProviderBeanDefinitionParserTests.java @@ -56,7 +56,7 @@ public class LdapProviderBeanDefinitionParserTests { AuthenticationManager authenticationManager = this.appCtx.getBean(BeanIds.AUTHENTICATION_MANAGER, AuthenticationManager.class); Authentication auth = authenticationManager - .authenticate(UsernamePasswordAuthenticationToken.unauthenticated("ben", "benspassword")); + .authenticate(UsernamePasswordAuthenticationToken.unauthenticated("otherben", "otherbenspassword")); UserDetails ben = (UserDetails) auth.getPrincipal(); assertThat(ben.getAuthorities()).hasSize(3); } @@ -127,6 +127,27 @@ public class LdapProviderBeanDefinitionParserTests { assertThat(auth).isNotNull(); } + @Test + public void supportsShaPasswordEncoder() { + this.appCtx = new InMemoryXmlApplicationContext(""" + + + + + + + + + + """); + AuthenticationManager authenticationManager = this.appCtx.getBean(BeanIds.AUTHENTICATION_MANAGER, + AuthenticationManager.class); + Authentication auth = authenticationManager + .authenticate(UsernamePasswordAuthenticationToken.unauthenticated("ben", "benspassword")); + + assertThat(auth).isNotNull(); + } + @Test public void inetOrgContextMapperIsSupported() { this.appCtx = new InMemoryXmlApplicationContext( diff --git a/config/src/integration-test/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParserTests.java b/config/src/integration-test/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParserTests.java index cf5a5b16bd..c453fe1cef 100644 --- a/config/src/integration-test/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParserTests.java +++ b/config/src/integration-test/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParserTests.java @@ -26,7 +26,7 @@ import org.springframework.ldap.core.LdapTemplate; import org.springframework.security.config.BeanIds; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; -import org.springframework.security.ldap.server.ApacheDSContainer; +import org.springframework.security.ldap.server.UnboundIdContainer; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -92,9 +92,9 @@ public class LdapServerBeanDefinitionParserTests { @Test public void defaultLdifFileIsSuccessful() { this.appCtx = new InMemoryXmlApplicationContext(""); - ApacheDSContainer dsContainer = this.appCtx.getBean(ApacheDSContainer.class); + UnboundIdContainer dsContainer = this.appCtx.getBean(UnboundIdContainer.class); - assertThat(ReflectionTestUtils.getField(dsContainer, "ldifResources")).isEqualTo("classpath*:*.ldif"); + assertThat(ReflectionTestUtils.getField(dsContainer, "ldif")).isEqualTo("classpath*:*.ldif"); } private int getDefaultPort() throws IOException { diff --git a/config/src/integration-test/resources/logback-test.xml b/config/src/integration-test/resources/logback-test.xml index 473e666748..1024d52dec 100644 --- a/config/src/integration-test/resources/logback-test.xml +++ b/config/src/integration-test/resources/logback-test.xml @@ -7,7 +7,6 @@ - diff --git a/config/src/main/java/org/springframework/security/config/BeanIds.java b/config/src/main/java/org/springframework/security/config/BeanIds.java index fcf2e5fc1d..da398cac57 100644 --- a/config/src/main/java/org/springframework/security/config/BeanIds.java +++ b/config/src/main/java/org/springframework/security/config/BeanIds.java @@ -54,8 +54,6 @@ public abstract class BeanIds { public static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = PREFIX + "methodSecurityMetadataSourceAdvisor"; - public static final String EMBEDDED_APACHE_DS = PREFIX + "apacheDirectoryServerContainer"; - public static final String EMBEDDED_UNBOUNDID = PREFIX + "unboundidServerContainer"; public static final String CONTEXT_SOURCE = PREFIX + "securityContextSource"; diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/ldap/LdapAuthenticationProviderConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/ldap/LdapAuthenticationProviderConfigurer.java index c54d6378ec..0f47afa594 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/ldap/LdapAuthenticationProviderConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/ldap/LdapAuthenticationProviderConfigurer.java @@ -37,7 +37,6 @@ import org.springframework.security.ldap.authentication.LdapAuthenticator; import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator; import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; import org.springframework.security.ldap.search.LdapUserSearch; -import org.springframework.security.ldap.server.ApacheDSContainer; import org.springframework.security.ldap.server.UnboundIdContainer; import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.InetOrgPersonContextMapper; @@ -60,12 +59,8 @@ import org.springframework.util.ClassUtils; public class LdapAuthenticationProviderConfigurer> extends SecurityConfigurerAdapter { - private static final String APACHEDS_CLASSNAME = "org.apache.directory.server.core.DefaultDirectoryService"; - private static final String UNBOUNDID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer"; - private static final boolean apacheDsPresent; - private static final boolean unboundIdPresent; private String groupRoleAttribute = "cn"; @@ -100,7 +95,6 @@ public class LdapAuthenticationProviderConfigurer with no Id), that server will be used. diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd index 4ff414800b..676b55a13d 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd @@ -224,13 +224,12 @@ - Explicitly specifies which embedded ldap server should use. Values are 'apacheds' and + Explicitly specifies which embedded ldap server should use. The only supported value is 'unboundid'. By default, it will depends if the library is available in the classpath. - diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfigurationResourceServerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfigurationResourceServerTests.java index 95f85c9be4..76001a2fac 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfigurationResourceServerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfigurationResourceServerTests.java @@ -21,7 +21,6 @@ import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import org.apache.commons.lang.StringUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -39,6 +38,7 @@ import org.springframework.security.oauth2.server.resource.web.reactive.function import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.client.WebClient; @@ -197,7 +197,7 @@ public class SecurityReactorContextConfigurationResourceServerTests { public MockResponse dispatch(RecordedRequest request) { MockResponse response = new MockResponse().setResponseCode(200); String header = request.getHeader("Authorization"); - if (StringUtils.isBlank(header)) { + if (!StringUtils.hasText(header)) { return response; } return response.setBody(header); 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 ccab6cb535..5607501eda 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 @@ -30,12 +30,12 @@ import java.util.TreeMap; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.lang.StringUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.security.config.http.SecurityFiltersAssertions; +import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -86,7 +86,7 @@ public class XsdDocumentedTests { .flatMap(XmlNode::children) .flatMap(XmlNode::children) .map((node) -> node.attribute("value")) - .filter(StringUtils::isNotEmpty) + .filter(StringUtils::hasText) .collect(Collectors.toList()); // @formatter:on SecurityFiltersAssertions.assertEquals(nodes); @@ -129,7 +129,7 @@ public class XsdDocumentedTests { .flatMap(XmlNode::children) .flatMap(XmlNode::children) .map((node) -> node.attribute("value")) - .filter(StringUtils::isNotEmpty) + .filter(StringUtils::hasText) .collect(Collectors.toList()); // @formatter:on assertThat(nodes).isEqualTo(expected); diff --git a/config/src/test/resources/logback-test.xml b/config/src/test/resources/logback-test.xml index fc35a34be1..e107f50bc8 100644 --- a/config/src/test/resources/logback-test.xml +++ b/config/src/test/resources/logback-test.xml @@ -7,8 +7,6 @@ - - diff --git a/dependencies/spring-security-dependencies.gradle b/dependencies/spring-security-dependencies.gradle index 2a2d433252..1a8be3ff73 100644 --- a/dependencies/spring-security-dependencies.gradle +++ b/dependencies/spring-security-dependencies.gradle @@ -49,12 +49,6 @@ dependencies { api libs.ldapsdk api libs.net.sourceforge.htmlunit api libs.org.htmlunit.htmlunit - api libs.org.apache.directory.server.apacheds.entry - api libs.org.apache.directory.server.apacheds.core - api libs.org.apache.directory.server.apacheds.protocol.ldap - api libs.org.apache.directory.server.apacheds.protocol.shared - api libs.org.apache.directory.server.apacheds.server.jndi - api libs.org.apache.directory.shared.shared.ldap api libs.org.apache.httpcomponents.httpclient api libs.org.aspectj.aspectjrt api libs.org.aspectj.aspectjweaver diff --git a/docs/modules/ROOT/pages/modules.adoc b/docs/modules/ROOT/pages/modules.adoc index df36c89014..12c2fca3b1 100644 --- a/docs/modules/ROOT/pages/modules.adoc +++ b/docs/modules/ROOT/pages/modules.adoc @@ -166,13 +166,9 @@ The top-level package is `org.springframework.security.ldap`. | | Data exception classes are required. -| apache-ds -| 1.5.5 -| Required if you are using an embedded LDAP server (optional). If you use `apache-ds`, the `apacheds-core`, `apacheds-core-entry`, `apacheds-protocol-shared`, `apacheds-protocol-ldap` and `apacheds-server-jndi` modules are required. - -| shared-ldap -| 0.9.15 -| Required if you are using an embedded LDAP server (optional). +| com.unboundid:unboundid-ldapsdk +| +| Required if using an embedded LDAP server | ldapsdk | 4.1 diff --git a/docs/modules/ROOT/pages/servlet/appendix/faq.adoc b/docs/modules/ROOT/pages/servlet/appendix/faq.adoc index d987b67642..0c72852891 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/faq.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/faq.adoc @@ -363,7 +363,7 @@ This section addresses common Spring Security architecture questions: . <> . <> . <> -. <> +. <> . <> @@ -412,30 +412,39 @@ The reference manual also includes <> that lists If you build your project with Maven, adding the appropriate Spring Security modules as dependencies to your `pom.xml` file automatically pulls in the core jars that the framework requires. Any that are marked as "`optional`" in the Spring Security `pom.xml` files have to be added to your own `pom.xml` file if you need them. +[[appendix-faq-unboundid-deps]] +=== What dependences are needed to run an embedded UnboundID LDAP server? + +You need to add the following dependency to your project: + +[tabs] +====== +Maven:: ++ +[source,maven,role="primary"] +---- + + com.unboundid + unboundid-ldapsdk + 7.0.1 + runtime + +---- + +Gradle:: ++ +[source,gradle,role="secondary"] +---- +implementation 'com.unboundid:unboundid-ldapsdk:7.0.1' +---- +====== + + [[appendix-faq-apacheds-deps]] === What dependencies are needed to run an embedded ApacheDS LDAP server? -If you use Maven, you need to add the following to your `pom.xml` file dependencies: - -[source] ----- - - - org.apache.directory.server - apacheds-core - 1.5.5 - runtime - - - org.apache.directory.server - apacheds-server-jndi - 1.5.5 - runtime - - ----- - -The other required jars should be pulled in transitively. +Spring Security 7 removes support for Apache DS. +Please use <> instead. [[appendix-faq-what-is-userdetailservice]] === What is a UserDetailsService and do I need one? diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/ldap.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/ldap.adoc index f3c07e6d76..819189b0b3 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/ldap.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/ldap.adoc @@ -24,7 +24,7 @@ This is actually the bean `id` of the `ContextSource` instance, if you want to u [[nsa-ldap-server-mode]] * **mode** -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. +Explicitly specifies which embedded ldap server should use. The only supported value is `unboundid`. By default, it will depends if the library is available in the classpath. [[nsa-ldap-server-id]] * **id** diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc index de64a1d448..1ee0668e7a 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc @@ -223,82 +223,8 @@ fun ldapContainer(): UnboundIdContainer { [[servlet-authentication-ldap-apacheds]] === Embedded ApacheDS Server -[NOTE] -==== -Spring Security uses ApacheDS 1.x, which is no longer maintained. -Unfortunately, ApacheDS 2.x has only released milestone versions with no stable release. -Once a stable release of ApacheDS 2.x is available, we will consider updating. -==== - -If you wish to use https://directory.apache.org/apacheds/[Apache DS], specify the following dependencies: - -.ApacheDS Dependencies -[tabs] -====== -Maven:: -+ -[source,xml,role="primary",subs="+attributes"] ----- - - org.apache.directory.server - apacheds-core - {apacheds-core-version} - runtime - - - org.apache.directory.server - apacheds-server-jndi - {apacheds-core-version} - runtime - ----- - -Gradle:: -+ -[source,groovy,role="secondary",subs="+attributes"] ----- -depenendencies { - runtimeOnly "org.apache.directory.server:apacheds-core:{apacheds-core-version}" - runtimeOnly "org.apache.directory.server:apacheds-server-jndi:{apacheds-core-version}" -} ----- -====== - -You can then configure the Embedded LDAP Server: - -.Embedded LDAP Server Configuration -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -ApacheDSContainer ldapContainer() { - return new ApacheDSContainer("dc=springframework,dc=org", - "classpath:users.ldif"); -} ----- - -XML:: -+ -[source,xml,role="secondary"] ----- - ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -fun ldapContainer(): ApacheDSContainer { - return ApacheDSContainer("dc=springframework,dc=org", "classpath:users.ldif") -} ----- -====== +Spring Security 7 removes support for Apache DS. +Please use <> instead. [[servlet-authentication-ldap-contextsource]] == LDAP ContextSource diff --git a/docs/modules/ROOT/pages/servlet/configuration/xml-namespace.adoc b/docs/modules/ROOT/pages/servlet/configuration/xml-namespace.adoc index e6f2ed028d..ddf652799c 100644 --- a/docs/modules/ROOT/pages/servlet/configuration/xml-namespace.adoc +++ b/docs/modules/ROOT/pages/servlet/configuration/xml-namespace.adoc @@ -15,7 +15,7 @@ For example, adding the following element from the `security` namespace to an ap ---- -This is much simpler than wiring up the equivalent Apache Directory Server beans. +This is much simpler than wiring up the equivalent UnboundID Server beans. The most common alternative configuration requirements are supported by attributes on the `ldap-server` element, and the user is isolated from worrying about which beans they need to create and what the bean property names are. You can find out more about the use of the `ldap-server` element in the chapter on xref:servlet/authentication/passwords/ldap.adoc#servlet-authentication-ldap[LDAP Authentication]. A good XML editor while editing the application context file should provide information on the attributes and elements that are available. diff --git a/docs/spring-security-docs.gradle b/docs/spring-security-docs.gradle index 680ab725e9..23e8d0db91 100644 --- a/docs/spring-security-docs.gradle +++ b/docs/spring-security-docs.gradle @@ -45,7 +45,6 @@ dependencies { testImplementation libs.webauthn4j.core testImplementation 'org.jetbrains.kotlin:kotlin-reflect' testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - testImplementation 'org.apache.directory.server:apacheds-core' testImplementation 'org.springframework:spring-core' testImplementation 'org.springframework:spring-test' diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1969124ad4..acd27bc3f4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,6 @@ io-rsocket = "1.1.5" io-spring-javaformat = "0.0.46" io-spring-nohttp = "0.0.11" jakarta-websocket = "2.2.0" -org-apache-directory-server = "1.5.5" org-apache-maven-resolver = "1.9.23" org-aspectj = "1.9.24" org-bouncycastle = "1.80" @@ -50,12 +49,6 @@ jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4 ldapsdk = "ldapsdk:ldapsdk:4.1" net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0" org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.11.1" -org-apache-directory-server-apacheds-core = { module = "org.apache.directory.server:apacheds-core", version.ref = "org-apache-directory-server" } -org-apache-directory-server-apacheds-entry = { module = "org.apache.directory.server:apacheds-core-entry", version.ref = "org-apache-directory-server" } -org-apache-directory-server-apacheds-protocol-ldap = { module = "org.apache.directory.server:apacheds-protocol-ldap", version.ref = "org-apache-directory-server" } -org-apache-directory-server-apacheds-protocol-shared = { module = "org.apache.directory.server:apacheds-protocol-shared", version.ref = "org-apache-directory-server" } -org-apache-directory-server-apacheds-server-jndi = { module = "org.apache.directory.server:apacheds-server-jndi", version.ref = "org-apache-directory-server" } -org-apache-directory-shared-shared-ldap = "org.apache.directory.shared:shared-ldap:0.9.15" org-apache-httpcomponents-httpclient = "org.apache.httpcomponents:httpclient:4.5.14" org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.10" org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" } diff --git a/itest/ldap/embedded-ldap-apacheds-default/spring-security-itest-ldap-embedded-apacheds-default.gradle b/itest/ldap/embedded-ldap-apacheds-default/spring-security-itest-ldap-embedded-apacheds-default.gradle deleted file mode 100644 index b48ab908a9..0000000000 --- a/itest/ldap/embedded-ldap-apacheds-default/spring-security-itest-ldap-embedded-apacheds-default.gradle +++ /dev/null @@ -1,30 +0,0 @@ -apply plugin: 'io.spring.convention.spring-test' - -dependencies { - implementation platform(project(":spring-security-dependencies")) - implementation project(':spring-security-core') - implementation 'org.springframework:spring-beans' - implementation 'org.springframework:spring-context' - implementation 'org.springframework:spring-core' - implementation 'org.springframework:spring-tx' - implementation project(':spring-security-config') - implementation project(':spring-security-ldap') - - runtimeOnly "org.apache.directory.server:apacheds-core" - runtimeOnly "org.apache.directory.server:apacheds-core-entry" - runtimeOnly "org.apache.directory.server:apacheds-protocol-shared" - runtimeOnly "org.apache.directory.server:apacheds-protocol-ldap" - runtimeOnly "org.apache.directory.server:apacheds-server-jndi" - runtimeOnly 'org.apache.directory.shared:shared-ldap' - - testImplementation project(path : ':spring-security-ldap', configuration : 'tests') - testImplementation "org.assertj:assertj-core" - testImplementation "org.junit.jupiter:junit-jupiter-api" - testImplementation "org.junit.jupiter:junit-jupiter-params" - testImplementation "org.junit.jupiter:junit-jupiter-engine" - testImplementation "org.mockito:mockito-core" - testImplementation "org.mockito:mockito-junit-jupiter" - testImplementation "org.springframework:spring-test" - - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' -} diff --git a/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/java/org/springframework/security/LdapServerBeanDefinitionParserTests.java b/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/java/org/springframework/security/LdapServerBeanDefinitionParserTests.java deleted file mode 100644 index 4f2be179a3..0000000000 --- a/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/java/org/springframework/security/LdapServerBeanDefinitionParserTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.security.config.BeanIds; -import org.springframework.security.ldap.server.ApacheDSContainer; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Eddú Meléndez - */ -public class LdapServerBeanDefinitionParserTests { - - private ClassPathXmlApplicationContext context; - - @BeforeEach - public void setup() { - this.context = new ClassPathXmlApplicationContext("applicationContext-security.xml"); - } - - @AfterEach - public void closeAppContext() { - if (this.context != null) { - this.context.close(); - this.context = null; - } - } - - @Test - public void apacheDirectoryServerIsStartedByDefault() { - String[] beanNames = this.context.getBeanNamesForType(ApacheDSContainer.class); - assertThat(beanNames).hasSize(1); - assertThat(beanNames[0]).isEqualTo(BeanIds.EMBEDDED_APACHE_DS); - } - -} diff --git a/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/applicationContext-security.xml b/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/applicationContext-security.xml deleted file mode 100644 index b1b80079c5..0000000000 --- a/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/applicationContext-security.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/users.ldif b/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/users.ldif deleted file mode 100644 index 222e03793c..0000000000 --- a/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/users.ldif +++ /dev/null @@ -1,60 +0,0 @@ -dn: ou=groups,dc=springframework,dc=org -objectclass: top -objectclass: organizationalUnit -ou: groups - -dn: ou=people,dc=springframework,dc=org -objectclass: top -objectclass: organizationalUnit -ou: people - -dn: uid=rod,ou=people,dc=springframework,dc=org -objectclass: top -objectclass: person -objectclass: organizationalPerson -objectclass: inetOrgPerson -cn: Rod Johnson -sn: Johnson -uid: rod -userPassword: koala - -dn: uid=dianne,ou=people,dc=springframework,dc=org -objectclass: top -objectclass: person -objectclass: organizationalPerson -objectclass: inetOrgPerson -cn: Dianne Emu -sn: Emu -uid: dianne -userPassword: emu - -dn: uid=scott,ou=people,dc=springframework,dc=org -objectclass: top -objectclass: person -objectclass: organizationalPerson -objectclass: inetOrgPerson -cn: Scott -sn: Wombat -uid: scott -userPassword: wombat - -dn: cn=user,ou=groups,dc=springframework,dc=org -objectclass: top -objectclass: groupOfNames -cn: user -member: uid=rod,ou=people,dc=springframework,dc=org -member: uid=dianne,ou=people,dc=springframework,dc=org -member: uid=scott,ou=people,dc=springframework,dc=org - -dn: cn=teller,ou=groups,dc=springframework,dc=org -objectclass: top -objectclass: groupOfNames -cn: teller -member: uid=rod,ou=people,dc=springframework,dc=org -member: uid=dianne,ou=people,dc=springframework,dc=org - -dn: cn=supervisor,ou=groups,dc=springframework,dc=org -objectclass: top -objectclass: groupOfNames -cn: supervisor -member: uid=rod,ou=people,dc=springframework,dc=org diff --git a/itest/ldap/embedded-ldap-mode-apacheds/spring-security-itest-ldap-embedded-mode-apacheds.gradle b/itest/ldap/embedded-ldap-mode-apacheds/spring-security-itest-ldap-embedded-mode-apacheds.gradle deleted file mode 100644 index b48ab908a9..0000000000 --- a/itest/ldap/embedded-ldap-mode-apacheds/spring-security-itest-ldap-embedded-mode-apacheds.gradle +++ /dev/null @@ -1,30 +0,0 @@ -apply plugin: 'io.spring.convention.spring-test' - -dependencies { - implementation platform(project(":spring-security-dependencies")) - implementation project(':spring-security-core') - implementation 'org.springframework:spring-beans' - implementation 'org.springframework:spring-context' - implementation 'org.springframework:spring-core' - implementation 'org.springframework:spring-tx' - implementation project(':spring-security-config') - implementation project(':spring-security-ldap') - - runtimeOnly "org.apache.directory.server:apacheds-core" - runtimeOnly "org.apache.directory.server:apacheds-core-entry" - runtimeOnly "org.apache.directory.server:apacheds-protocol-shared" - runtimeOnly "org.apache.directory.server:apacheds-protocol-ldap" - runtimeOnly "org.apache.directory.server:apacheds-server-jndi" - runtimeOnly 'org.apache.directory.shared:shared-ldap' - - testImplementation project(path : ':spring-security-ldap', configuration : 'tests') - testImplementation "org.assertj:assertj-core" - testImplementation "org.junit.jupiter:junit-jupiter-api" - testImplementation "org.junit.jupiter:junit-jupiter-params" - testImplementation "org.junit.jupiter:junit-jupiter-engine" - testImplementation "org.mockito:mockito-core" - testImplementation "org.mockito:mockito-junit-jupiter" - testImplementation "org.springframework:spring-test" - - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' -} diff --git a/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/java/org/springframework/security/LdapServerBeanDefinitionParserTests.java b/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/java/org/springframework/security/LdapServerBeanDefinitionParserTests.java deleted file mode 100644 index 4f2be179a3..0000000000 --- a/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/java/org/springframework/security/LdapServerBeanDefinitionParserTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.security.config.BeanIds; -import org.springframework.security.ldap.server.ApacheDSContainer; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Eddú Meléndez - */ -public class LdapServerBeanDefinitionParserTests { - - private ClassPathXmlApplicationContext context; - - @BeforeEach - public void setup() { - this.context = new ClassPathXmlApplicationContext("applicationContext-security.xml"); - } - - @AfterEach - public void closeAppContext() { - if (this.context != null) { - this.context.close(); - this.context = null; - } - } - - @Test - public void apacheDirectoryServerIsStartedByDefault() { - String[] beanNames = this.context.getBeanNamesForType(ApacheDSContainer.class); - assertThat(beanNames).hasSize(1); - assertThat(beanNames[0]).isEqualTo(BeanIds.EMBEDDED_APACHE_DS); - } - -} diff --git a/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/applicationContext-security.xml b/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/applicationContext-security.xml deleted file mode 100644 index 8e3f4b4380..0000000000 --- a/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/applicationContext-security.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/users.ldif b/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/users.ldif deleted file mode 100644 index 222e03793c..0000000000 --- a/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/users.ldif +++ /dev/null @@ -1,60 +0,0 @@ -dn: ou=groups,dc=springframework,dc=org -objectclass: top -objectclass: organizationalUnit -ou: groups - -dn: ou=people,dc=springframework,dc=org -objectclass: top -objectclass: organizationalUnit -ou: people - -dn: uid=rod,ou=people,dc=springframework,dc=org -objectclass: top -objectclass: person -objectclass: organizationalPerson -objectclass: inetOrgPerson -cn: Rod Johnson -sn: Johnson -uid: rod -userPassword: koala - -dn: uid=dianne,ou=people,dc=springframework,dc=org -objectclass: top -objectclass: person -objectclass: organizationalPerson -objectclass: inetOrgPerson -cn: Dianne Emu -sn: Emu -uid: dianne -userPassword: emu - -dn: uid=scott,ou=people,dc=springframework,dc=org -objectclass: top -objectclass: person -objectclass: organizationalPerson -objectclass: inetOrgPerson -cn: Scott -sn: Wombat -uid: scott -userPassword: wombat - -dn: cn=user,ou=groups,dc=springframework,dc=org -objectclass: top -objectclass: groupOfNames -cn: user -member: uid=rod,ou=people,dc=springframework,dc=org -member: uid=dianne,ou=people,dc=springframework,dc=org -member: uid=scott,ou=people,dc=springframework,dc=org - -dn: cn=teller,ou=groups,dc=springframework,dc=org -objectclass: top -objectclass: groupOfNames -cn: teller -member: uid=rod,ou=people,dc=springframework,dc=org -member: uid=dianne,ou=people,dc=springframework,dc=org - -dn: cn=supervisor,ou=groups,dc=springframework,dc=org -objectclass: top -objectclass: groupOfNames -cn: supervisor -member: uid=rod,ou=people,dc=springframework,dc=org diff --git a/ldap/spring-security-ldap.gradle b/ldap/spring-security-ldap.gradle index d15029ffdd..f816af7ab8 100644 --- a/ldap/spring-security-ldap.gradle +++ b/ldap/spring-security-ldap.gradle @@ -15,12 +15,6 @@ dependencies { optional 'com.fasterxml.jackson.core:jackson-databind' optional 'ldapsdk:ldapsdk' optional "com.unboundid:unboundid-ldapsdk" - optional "org.apache.directory.server:apacheds-core" - optional "org.apache.directory.server:apacheds-core-entry" - optional "org.apache.directory.server:apacheds-protocol-shared" - optional "org.apache.directory.server:apacheds-protocol-ldap" - optional "org.apache.directory.server:apacheds-server-jndi" - optional 'org.apache.directory.shared:shared-ldap' api ('org.springframework.ldap:spring-ldap-core') { exclude(group: 'commons-logging', module: 'commons-logging') exclude(group: 'org.springframework', module: 'spring-beans') diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/DefaultSpringSecurityContextSourceTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/DefaultSpringSecurityContextSourceTests.java index 607b0a2414..f03af31b4d 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/DefaultSpringSecurityContextSourceTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/DefaultSpringSecurityContextSourceTests.java @@ -38,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException * @author Eddú Meléndez */ @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = ApacheDsContainerConfig.class) +@ContextConfiguration(classes = UnboundIdContainerConfig.class) public class DefaultSpringSecurityContextSourceTests { @Autowired diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/SpringSecurityLdapTemplateITests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/SpringSecurityLdapTemplateITests.java index 5b7ca9f69f..25d65e1215 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/SpringSecurityLdapTemplateITests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/SpringSecurityLdapTemplateITests.java @@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * @author Eddú Meléndez */ @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = ApacheDsContainerConfig.class) +@ContextConfiguration(classes = UnboundIdContainerConfig.class) public class SpringSecurityLdapTemplateITests { @Autowired @@ -116,10 +116,10 @@ public class SpringSecurityLdapTemplateITests { assertThat(values).hasSize(1); Map> record = values.iterator().next(); assertAttributeValue(record, "uid", "bob"); - assertAttributeValue(record, "objectclass", "top", "person", "organizationalPerson", "inetOrgPerson"); + assertAttributeValue(record, "objectClass", "top", "person", "organizationalPerson", "inetOrgPerson"); assertAttributeValue(record, "cn", "Bob Hamilton"); assertAttributeValue(record, "sn", "Hamilton"); - assertThat(record.containsKey("userPassword")).isFalse(); + assertThat(record.containsKey("userPassword")).isTrue(); } @Test @@ -129,10 +129,10 @@ public class SpringSecurityLdapTemplateITests { assertThat(values).hasSize(1); Map> record = values.iterator().next(); assertAttributeValue(record, "uid", "bob"); - assertAttributeValue(record, "objectclass", "top", "person", "organizationalPerson", "inetOrgPerson"); + assertAttributeValue(record, "objectClass", "top", "person", "organizationalPerson", "inetOrgPerson"); assertAttributeValue(record, "cn", "Bob Hamilton"); assertAttributeValue(record, "sn", "Hamilton"); - assertThat(record.containsKey("userPassword")).isFalse(); + assertThat(record.containsKey("userPassword")).isTrue(); } @Test @@ -145,7 +145,7 @@ public class SpringSecurityLdapTemplateITests { assertAttributeValue(record, "cn", "Bob Hamilton"); assertAttributeValue(record, "sn", "Hamilton"); assertThat(record.containsKey("userPassword")).isFalse(); - assertThat(record.containsKey("objectclass")).isFalse(); + assertThat(record.containsKey("objectClass")).isFalse(); } protected void assertAttributeValue(Map> record, String attributeName, String... values) { diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/ApacheDsContainerConfig.java b/ldap/src/integration-test/java/org/springframework/security/ldap/UnboundIdContainerConfig.java similarity index 67% rename from ldap/src/integration-test/java/org/springframework/security/ldap/ApacheDsContainerConfig.java rename to ldap/src/integration-test/java/org/springframework/security/ldap/UnboundIdContainerConfig.java index 41478863d6..b56abddd62 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/ApacheDsContainerConfig.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/UnboundIdContainerConfig.java @@ -20,31 +20,31 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.ldap.core.ContextSource; -import org.springframework.security.ldap.server.ApacheDSContainer; +import org.springframework.security.ldap.server.UnboundIdContainer; /** * @author Eddú Meléndez */ @Configuration -public class ApacheDsContainerConfig implements DisposableBean { +public class UnboundIdContainerConfig implements DisposableBean { - private ApacheDSContainer container; + private UnboundIdContainer container; @Bean - ApacheDSContainer ldapContainer() throws Exception { - this.container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); + UnboundIdContainer ldapContainer() { + this.container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); this.container.setPort(0); return this.container; } @Bean - ContextSource contextSource(ApacheDSContainer ldapContainer) throws Exception { + ContextSource contextSource(UnboundIdContainer ldapContainer) { return new DefaultSpringSecurityContextSource( - "ldap://127.0.0.1:" + ldapContainer.getLocalPort() + "/dc=springframework,dc=org"); + "ldap://127.0.0.1:" + ldapContainer.getPort() + "/dc=springframework,dc=org"); } @Override - public void destroy() throws Exception { + public void destroy() { this.container.stop(); } diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/authentication/BindAuthenticatorTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/authentication/BindAuthenticatorTests.java index 95e73bd217..46f6d71fb0 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/authentication/BindAuthenticatorTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/authentication/BindAuthenticatorTests.java @@ -33,8 +33,8 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.SpringSecurityMessageSource; -import org.springframework.security.ldap.ApacheDsContainerConfig; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.security.ldap.UnboundIdContainerConfig; import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -53,7 +53,7 @@ import static org.mockito.Mockito.spy; * @author Eddú Meléndez */ @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = ApacheDsContainerConfig.class) +@ContextConfiguration(classes = UnboundIdContainerConfig.class) public class BindAuthenticatorTests { @Autowired diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/authentication/PasswordComparisonAuthenticatorTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/authentication/PasswordComparisonAuthenticatorTests.java index 5ba4a7d72c..a7598b3c4e 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/authentication/PasswordComparisonAuthenticatorTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/authentication/PasswordComparisonAuthenticatorTests.java @@ -30,8 +30,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.keygen.KeyGenerators; import org.springframework.security.crypto.password.LdapShaPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.ldap.ApacheDsContainerConfig; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.security.ldap.UnboundIdContainerConfig; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException * @author Eddú Meléndez */ @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = ApacheDsContainerConfig.class) +@ContextConfiguration(classes = UnboundIdContainerConfig.class) public class PasswordComparisonAuthenticatorTests { @Autowired diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchTests.java index 5dd196fb02..57b2f3bddb 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchTests.java @@ -25,8 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.ldap.core.DirContextOperations; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.ldap.ApacheDsContainerConfig; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.security.ldap.UnboundIdContainerConfig; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * @author Eddú Meléndez */ @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = ApacheDsContainerConfig.class) +@ContextConfiguration(classes = UnboundIdContainerConfig.class) public class FilterBasedLdapUserSearchTests { @Autowired diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchWithSpacesTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchWithSpacesTests.java index 6db08933fa..9b790f711b 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchWithSpacesTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchWithSpacesTests.java @@ -28,7 +28,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.DirContextOperations; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; -import org.springframework.security.ldap.server.ApacheDSContainer; +import org.springframework.security.ldap.server.UnboundIdContainer; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Steve Riesenberg */ @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = FilterBasedLdapUserSearchWithSpacesTests.ApacheDsContainerWithSpacesConfig.class) +@ContextConfiguration(classes = FilterBasedLdapUserSearchWithSpacesTests.UnboundIdContainerWithSpacesConfig.class) public class FilterBasedLdapUserSearchWithSpacesTests { @Autowired @@ -61,22 +61,22 @@ public class FilterBasedLdapUserSearchWithSpacesTests { } @Configuration - static class ApacheDsContainerWithSpacesConfig implements DisposableBean { + static class UnboundIdContainerWithSpacesConfig implements DisposableBean { - private ApacheDSContainer container; + private UnboundIdContainer container; @Bean - ApacheDSContainer ldapContainer() throws Exception { - this.container = new ApacheDSContainer("dc=spring framework,dc=org", + UnboundIdContainer ldapContainer() { + this.container = new UnboundIdContainer("dc=spring framework,dc=org", "classpath:test-server-with-spaces.ldif"); this.container.setPort(0); return this.container; } @Bean - ContextSource contextSource(ApacheDSContainer ldapContainer) { + ContextSource contextSource(UnboundIdContainer ldapContainer) { return new DefaultSpringSecurityContextSource( - "ldap://127.0.0.1:" + ldapContainer.getLocalPort() + "/dc=spring%20framework,dc=org"); + "ldap://127.0.0.1:" + ldapContainer.getPort() + "/dc=spring%20framework,dc=org"); } @Override diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java deleted file mode 100644 index f4f3953a19..0000000000 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.server; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.ServerSocket; -import java.security.UnrecoverableKeyException; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.util.FileCopyUtils; - -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.fail; - -/** - * Useful for debugging the container by itself. - * - * @author Luke Taylor - * @author Rob Winch - * @author Gunnar Hillert - * @author Evgeniy Cheban - * @since 3.0 - */ -public class ApacheDSContainerTests { - - @TempDir - public File temporaryFolder; - - // SEC-2162 - @Test - public void failsToStartThrowsException() throws Exception { - ApacheDSContainer server1 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); - ApacheDSContainer server2 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:missing.ldif"); - List ports = getDefaultPorts(1); - server1.setPort(ports.get(0)); - server2.setPort(ports.get(0)); - try { - server1.afterPropertiesSet(); - try { - server2.afterPropertiesSet(); - fail("Expected Exception"); - } - catch (RuntimeException success) { - } - } - finally { - try { - server1.destroy(); - } - catch (Throwable ex) { - } - try { - server2.destroy(); - } - catch (Throwable ex) { - } - } - } - - // SEC-2161 - @Test - public void multipleInstancesSimultanciously() throws Exception { - ApacheDSContainer server1 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); - ApacheDSContainer server2 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); - List ports = getDefaultPorts(2); - server1.setPort(ports.get(0)); - server2.setPort(ports.get(1)); - try { - server1.afterPropertiesSet(); - server2.afterPropertiesSet(); - } - finally { - try { - server1.destroy(); - } - catch (Throwable ex) { - } - try { - server2.destroy(); - } - catch (Throwable ex) { - } - } - } - - @Test - public void startWithLdapOverSslWithoutCertificate() throws Exception { - ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); - List ports = getDefaultPorts(1); - server.setPort(ports.get(0)); - server.setLdapOverSslEnabled(true); - assertThatIllegalArgumentException().isThrownBy(server::afterPropertiesSet) - .withMessage("When LdapOverSsl is enabled, the keyStoreFile property must be set."); - } - - @Test - @DisabledOnOs(OS.WINDOWS) - public void startWithLdapOverSslWithWrongPassword() throws Exception { - final ClassPathResource keyStoreResource = new ClassPathResource( - "/org/springframework/security/ldap/server/spring.keystore"); - final File temporaryKeyStoreFile = new File(this.temporaryFolder, "spring.keystore"); - FileCopyUtils.copy(keyStoreResource.getInputStream(), new FileOutputStream(temporaryKeyStoreFile)); - - assertThat(temporaryKeyStoreFile).isFile(); - - ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); - - List ports = getDefaultPorts(1); - server.setPort(ports.get(0)); - - server.setLdapOverSslEnabled(true); - server.setKeyStoreFile(temporaryKeyStoreFile); - server.setCertificatePassord("incorrect-password"); - assertThatExceptionOfType(RuntimeException.class).isThrownBy(server::afterPropertiesSet) - .withMessage("Server startup failed") - .withRootCauseInstanceOf(UnrecoverableKeyException.class); - } - - /** - * This test starts an LDAP server using LDAPs (LDAP over SSL). A self-signed - * certificate is being used, which was previously generated with: - * - *

-	 * {@code
-	 * keytool -genkey -alias spring -keyalg RSA -keystore spring.keystore -validity 3650 -storetype JKS \
-	 * -dname "CN=localhost, OU=Spring, O=Pivotal, L=Kailua-Kona, ST=HI, C=US" -keypass spring -storepass spring
-	 * }
-	 * 
- * @throws Exception - */ - @Test - @DisabledOnOs(OS.WINDOWS) - public void startWithLdapOverSsl() throws Exception { - - final ClassPathResource keyStoreResource = new ClassPathResource( - "/org/springframework/security/ldap/server/spring.keystore"); - final File temporaryKeyStoreFile = new File(this.temporaryFolder, "spring.keystore"); - FileCopyUtils.copy(keyStoreResource.getInputStream(), new FileOutputStream(temporaryKeyStoreFile)); - - assertThat(temporaryKeyStoreFile).isFile(); - - ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); - - List ports = getDefaultPorts(1); - server.setPort(ports.get(0)); - - server.setLdapOverSslEnabled(true); - server.setKeyStoreFile(temporaryKeyStoreFile); - server.setCertificatePassord("spring"); - - try { - server.afterPropertiesSet(); - } - finally { - try { - server.destroy(); - } - catch (Throwable ex) { - } - } - } - - private List getDefaultPorts(int count) throws IOException { - List connections = new ArrayList<>(); - List availablePorts = new ArrayList<>(count); - try { - for (int i = 0; i < count; i++) { - ServerSocket socket = new ServerSocket(0); - connections.add(socket); - availablePorts.add(socket.getLocalPort()); - } - return availablePorts; - } - finally { - for (ServerSocket conn : connections) { - conn.close(); - } - } - } - - @Test - public void afterPropertiesSetWhenPortIsZeroThenRandomPortIsSelected() throws Exception { - ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); - server.setPort(0); - try { - server.afterPropertiesSet(); - - assertThat(server.getPort()).isEqualTo(0); - assertThat(server.getLocalPort()).isNotEqualTo(0); - } - finally { - server.destroy(); - } - } - -} diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSEmbeddedLdifTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSEmbeddedLdifTests.java deleted file mode 100644 index 232f241845..0000000000 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSEmbeddedLdifTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.server; - -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.springframework.ldap.core.support.LdapContextSource; -import org.springframework.security.ldap.SpringSecurityLdapTemplate; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests reproducing problems with loading structures from ldif on embedded ApacheDS - * server. - * - * @author Marcin Zajączkowski - */ -public class ApacheDSEmbeddedLdifTests { - - private static final String LDAP_ROOT = "ou=ssattributes,dc=springframework,dc=org"; - - private static final int LDAP_PORT = 52389; - - private ApacheDSContainer server; - - private SpringSecurityLdapTemplate ldapTemplate; - - @BeforeEach - public void setUp() throws Exception { - // TODO: InMemoryXmlApplicationContext would be useful here, but it is not visible - this.server = new ApacheDSContainer(LDAP_ROOT, "classpath:test-server-custom-attribute-types.ldif"); - this.server.setPort(LDAP_PORT); - this.server.afterPropertiesSet(); - - this.ldapTemplate = new SpringSecurityLdapTemplate(createLdapContextSource()); - } - - private LdapContextSource createLdapContextSource() { - LdapContextSource ldapContextSource = new LdapContextSource(); - ldapContextSource.setUrl("ldap://localhost:" + LDAP_PORT); - ldapContextSource.setBase(LDAP_ROOT); - ldapContextSource.afterPropertiesSet(); - return ldapContextSource; - } - - @AfterEach - public void tearDown() throws Exception { - if (this.server != null) { - this.server.destroy(); - } - } - - @Disabled // Not fixed yet - @Test // SEC-2387 - public void customAttributeTypesShouldBeProperlyCreatedWhenLoadedFromLdif() { - assertThat(this.ldapTemplate.compare("uid=objectWithCustomAttribute1", "uid", "objectWithCustomAttribute1")) - .isTrue(); - assertThat(this.ldapTemplate.compare("uid=objectWithCustomAttribute1", "customAttribute", "I am custom")) - .isTrue(); - } - -} diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.java index 95181b7651..f48f4c90b6 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.java @@ -31,7 +31,7 @@ import org.springframework.ldap.core.DirContextAdapter; import org.springframework.ldap.core.DistinguishedName; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; -import org.springframework.security.ldap.server.ApacheDSContainer; +import org.springframework.security.ldap.server.UnboundIdContainer; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -42,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ @ExtendWith(SpringExtension.class) @ContextConfiguration( - classes = DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.ApacheDsContainerWithUndefinedGroupRoleAttributeConfig.class) + classes = DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.UnboundIdContainerWithUndefinedGroupRoleAttributeConfig.class) public class DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests { @Autowired @@ -77,22 +77,22 @@ public class DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests { } @Configuration - static class ApacheDsContainerWithUndefinedGroupRoleAttributeConfig implements DisposableBean { + static class UnboundIdContainerWithUndefinedGroupRoleAttributeConfig implements DisposableBean { - private ApacheDSContainer container; + private UnboundIdContainer container; @Bean - ApacheDSContainer ldapContainer() throws Exception { - this.container = new ApacheDSContainer("dc=springframework,dc=org", + UnboundIdContainer ldapContainer() { + this.container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:test-server-with-undefined-group-role-attributes.ldif"); this.container.setPort(0); return this.container; } @Bean - ContextSource contextSource(ApacheDSContainer ldapContainer) { + ContextSource contextSource(UnboundIdContainer ldapContainer) { return new DefaultSpringSecurityContextSource( - "ldap://127.0.0.1:" + ldapContainer.getLocalPort() + "/dc=springframework,dc=org"); + "ldap://127.0.0.1:" + ldapContainer.getPort() + "/dc=springframework,dc=org"); } @Override diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorTests.java index 1d6e2d6664..a7fc6aafc5 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorTests.java @@ -31,8 +31,8 @@ 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.ldap.ApacheDsContainerConfig; import org.springframework.security.ldap.SpringSecurityLdapTemplate; +import org.springframework.security.ldap.UnboundIdContainerConfig; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -44,7 +44,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException * @author Eddú Meléndez */ @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = ApacheDsContainerConfig.class) +@ContextConfiguration(classes = UnboundIdContainerConfig.class) @SuppressWarnings({ "deprecation" }) public class DefaultLdapAuthoritiesPopulatorTests { 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 0a53171efb..df1f8b6e39 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 @@ -34,9 +34,9 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.ldap.ApacheDsContainerConfig; import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper; import org.springframework.security.ldap.SpringSecurityLdapTemplate; +import org.springframework.security.ldap.UnboundIdContainerConfig; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -52,7 +52,7 @@ import static org.mockito.Mockito.verify; * @author Roman Zabaluev */ @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = ApacheDsContainerConfig.class) +@ContextConfiguration(classes = UnboundIdContainerConfig.class) public class LdapUserDetailsManagerTests { @Autowired diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulatorTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulatorTests.java index aec53f235c..3139673b43 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulatorTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulatorTests.java @@ -28,7 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.DirContextAdapter; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.ldap.ApacheDsContainerConfig; +import org.springframework.security.ldap.UnboundIdContainerConfig; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Eddú Meléndez */ @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = ApacheDsContainerConfig.class) +@ContextConfiguration(classes = UnboundIdContainerConfig.class) public class NestedLdapAuthoritiesPopulatorTests { @Autowired diff --git a/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java b/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java deleted file mode 100644 index 1fe397ac51..0000000000 --- a/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.server; - -import java.io.File; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.directory.server.core.DefaultDirectoryService; -import org.apache.directory.server.core.authn.AuthenticationInterceptor; -import org.apache.directory.server.core.entry.ServerEntry; -import org.apache.directory.server.core.exception.ExceptionInterceptor; -import org.apache.directory.server.core.interceptor.Interceptor; -import org.apache.directory.server.core.normalization.NormalizationInterceptor; -import org.apache.directory.server.core.operational.OperationalAttributeInterceptor; -import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition; -import org.apache.directory.server.core.referral.ReferralInterceptor; -import org.apache.directory.server.core.subtree.SubentryInterceptor; -import org.apache.directory.server.ldap.LdapServer; -import org.apache.directory.server.protocol.shared.store.LdifFileLoader; -import org.apache.directory.server.protocol.shared.transport.TcpTransport; -import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException; -import org.apache.directory.shared.ldap.name.LdapDN; -import org.apache.mina.transport.socket.SocketAcceptor; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.Lifecycle; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.util.Assert; - -/** - * Provides lifecycle services for the embedded apacheDS server defined by the supplied - * configuration. Used by {@code LdapServerBeanDefinitionParser}. An instance will be - * stored in the application context for each embedded server instance. It will start the - * server when the context is initialized and shut it down when it is closed. It is - * intended for temporary embedded use and will not retain changes across start/stop - * boundaries. The working directory is deleted on shutdown. - * - *

- * If used repeatedly in a single JVM process with the same configuration (for example, - * when repeatedly loading an application context during testing), it's important that the - * application context is closed to allow the bean to be disposed of and the server - * shutdown prior to attempting to start it again. - *

- * This class is intended for testing and internal security namespace use, only, and is - * not considered part of the framework's public API. - * - * @author Luke Taylor - * @author Rob Winch - * @author Gunnar Hillert - * @author Evgeniy Cheban - * @deprecated For removal in 7.0. Use {@link UnboundIdContainer} instead because ApacheDS - * 1.x is no longer supported with no GA version to replace it. - */ -@Deprecated(since = "5.2", forRemoval = true) -public class ApacheDSContainer - implements EmbeddedLdapServerContainer, InitializingBean, DisposableBean, Lifecycle, ApplicationContextAware { - - private final Log logger = LogFactory.getLog(getClass()); - - final DefaultDirectoryService service; - - LdapServer server; - - private TcpTransport transport; - - private ApplicationContext ctxt; - - private File workingDir; - - private boolean running; - - private final String ldifResources; - - private final JdbmPartition partition; - - private final String root; - - private int port = 53389; - - private int localPort; - - private boolean ldapOverSslEnabled; - - private File keyStoreFile; - - private String certificatePassord; - - public ApacheDSContainer(String root, String ldifs) throws Exception { - this.ldifResources = ldifs; - this.service = new DefaultDirectoryService(); - List list = new ArrayList<>(); - list.add(new NormalizationInterceptor()); - list.add(new AuthenticationInterceptor()); - list.add(new ReferralInterceptor()); - list.add(new ExceptionInterceptor()); - list.add(new OperationalAttributeInterceptor()); - list.add(new SubentryInterceptor()); - this.service.setInterceptors(list); - this.partition = new JdbmPartition(); - this.partition.setId("rootPartition"); - this.partition.setSuffix(root); - this.root = root; - this.service.addPartition(this.partition); - this.service.setExitVmOnShutdown(false); - this.service.setShutdownHookEnabled(false); - this.service.getChangeLog().setEnabled(false); - this.service.setDenormalizeOpAttrsEnabled(true); - } - - @Override - public void afterPropertiesSet() throws Exception { - if (this.workingDir == null) { - String apacheWorkDir = System.getProperty("apacheDSWorkDir"); - if (apacheWorkDir == null) { - apacheWorkDir = createTempDirectory("apacheds-spring-security-"); - } - setWorkingDirectory(new File(apacheWorkDir)); - } - Assert.isTrue(!this.ldapOverSslEnabled || this.keyStoreFile != null, - "When LdapOverSsl is enabled, the keyStoreFile property must be set."); - this.server = new LdapServer(); - this.server.setDirectoryService(this.service); - // AbstractLdapIntegrationTests assume IPv4, so we specify the same here - this.transport = new TcpTransport(this.port); - if (this.ldapOverSslEnabled) { - this.transport.setEnableSSL(true); - this.server.setKeystoreFile(this.keyStoreFile.getAbsolutePath()); - this.server.setCertificatePassword(this.certificatePassord); - } - this.server.setTransports(this.transport); - start(); - } - - @Override - public void destroy() { - stop(); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.ctxt = applicationContext; - } - - public void setWorkingDirectory(File workingDir) { - Assert.notNull(workingDir, "workingDir cannot be null"); - this.logger.info("Setting working directory for LDAP_PROVIDER: " + workingDir.getAbsolutePath()); - Assert.isTrue(!workingDir.exists(), - "The specified working directory '" + workingDir.getAbsolutePath() - + "' already exists. Another directory service instance may be using it or it may be from a " - + " previous unclean shutdown. Please confirm and delete it or configure a different " - + "working directory"); - this.workingDir = workingDir; - this.service.setWorkingDirectory(workingDir); - } - - @Override - public void setPort(int port) { - this.port = port; - } - - @Override - public int getPort() { - return this.port; - } - - /** - * Returns the port that is resolved by {@link TcpTransport}. - * @return the port that is resolved by {@link TcpTransport} - */ - public int getLocalPort() { - return this.localPort; - } - - /** - * If set to {@code true} will enable LDAP over SSL (LDAPs). If set to {@code true} - * {@link ApacheDSContainer#setCertificatePassord(String)} must be set as well. - * @param ldapOverSslEnabled If not set, will default to false - */ - public void setLdapOverSslEnabled(boolean ldapOverSslEnabled) { - this.ldapOverSslEnabled = ldapOverSslEnabled; - } - - /** - * The keyStore must not be null and must be a valid file. Will set the keyStore file - * on the underlying {@link LdapServer}. - * @param keyStoreFile Mandatory if LDAPs is enabled - */ - public void setKeyStoreFile(File keyStoreFile) { - Assert.notNull(keyStoreFile, "The keyStoreFile must not be null."); - Assert.isTrue(keyStoreFile.isFile(), "The keyStoreFile must be a file."); - this.keyStoreFile = keyStoreFile; - } - - /** - * Will set the certificate password on the underlying {@link LdapServer}. - * @param certificatePassord May be null - */ - public void setCertificatePassord(String certificatePassord) { - this.certificatePassord = certificatePassord; - } - - public DefaultDirectoryService getService() { - return this.service; - } - - @Override - public void start() { - if (isRunning()) { - return; - } - Assert.state(!this.service.isStarted(), "DirectoryService is already running."); - this.logger.info("Starting directory server..."); - try { - this.service.startup(); - this.server.start(); - } - catch (Exception ex) { - throw new RuntimeException("Server startup failed", ex); - } - try { - this.service.getAdminSession().lookup(this.partition.getSuffixDn()); - } - catch (LdapNameNotFoundException ex) { - handleLdapNameNotFoundException(); - } - catch (Exception ex) { - this.logger.error("Lookup failed", ex); - } - SocketAcceptor socketAcceptor = this.server.getSocketAcceptor(this.transport); - InetSocketAddress localAddress = socketAcceptor.getLocalAddress(); - this.localPort = localAddress.getPort(); - this.running = true; - try { - importLdifs(); - } - catch (Exception ex) { - throw new RuntimeException("Failed to import LDIF file(s)", ex); - } - } - - private void handleLdapNameNotFoundException() { - try { - LdapDN dn = new LdapDN(this.root); - Assert.isTrue(this.root.startsWith("dc="), "root must start with dc="); - String dc = this.root.substring(3, this.root.indexOf(',')); - ServerEntry entry = this.service.newEntry(dn); - entry.add("objectClass", "top", "domain", "extensibleObject"); - entry.add("dc", dc); - this.service.getAdminSession().add(entry); - } - catch (Exception ex) { - this.logger.error("Failed to create dc entry", ex); - } - } - - @Override - public void stop() { - if (!isRunning()) { - return; - } - this.logger.info("Shutting down directory server ..."); - try { - this.server.stop(); - this.service.shutdown(); - } - catch (Exception ex) { - this.logger.error("Shutdown failed", ex); - return; - } - this.running = false; - if (this.workingDir.exists()) { - this.logger.info("Deleting working directory " + this.workingDir.getAbsolutePath()); - deleteDir(this.workingDir); - } - } - - private void importLdifs() throws Exception { - // Import any ldif files - Resource[] ldifs = (this.ctxt != null) ? this.ctxt.getResources(this.ldifResources) - : new PathMatchingResourcePatternResolver().getResources(this.ldifResources); - // Note that we can't just import using the ServerContext returned - // from starting Apache DS, apparently because of the long-running issue - // DIRSERVER-169. - // We need a standard context. - // DirContext dirContext = contextSource.getReadWriteContext(); - if (ldifs == null || ldifs.length == 0) { - return; - } - Assert.isTrue(ldifs.length == 1, () -> "More than one LDIF resource found with the supplied pattern:" - + this.ldifResources + " Got " + Arrays.toString(ldifs)); - String ldifFile = getLdifFile(ldifs); - this.logger.info("Loading LDIF file: " + ldifFile); - LdifFileLoader loader = new LdifFileLoader(this.service.getAdminSession(), new File(ldifFile), null, - getClass().getClassLoader()); - loader.execute(); - } - - private String getLdifFile(Resource[] ldifs) throws IOException { - try { - return ldifs[0].getFile().getAbsolutePath(); - } - catch (IOException ex) { - return ldifs[0].getURI().toString(); - } - } - - private String createTempDirectory(String prefix) throws IOException { - String parentTempDir = System.getProperty("java.io.tmpdir"); - String fileNamePrefix = prefix + System.nanoTime(); - String fileName = fileNamePrefix; - for (int i = 0; i < 1000; i++) { - File tempDir = new File(parentTempDir, fileName); - if (!tempDir.exists()) { - return tempDir.getAbsolutePath(); - } - fileName = fileNamePrefix + "~" + i; - } - throw new IOException( - "Failed to create a temporary directory for file at " + new File(parentTempDir, fileNamePrefix)); - } - - private boolean deleteDir(File dir) { - if (dir.isDirectory()) { - String[] children = dir.list(); - for (String child : children) { - boolean success = deleteDir(new File(dir, child)); - if (!success) { - return false; - } - } - } - return dir.delete(); - } - - @Override - public boolean isRunning() { - return this.running; - } - -} diff --git a/ldap/src/main/java/org/springframework/security/ldap/server/package-info.java b/ldap/src/main/java/org/springframework/security/ldap/server/package-info.java index eb33468157..52f0ab3da2 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/server/package-info.java +++ b/ldap/src/main/java/org/springframework/security/ldap/server/package-info.java @@ -15,7 +15,6 @@ */ /** - * Embedded Apache Directory Server implementation, as used by the configuration - * namespace. + * Embedded UnboundID Server implementation, as used by the configuration namespace. */ package org.springframework.security.ldap.server; diff --git a/ldap/src/test/java/org/apache/directory/server/core/avltree/ArrayMarshaller.java b/ldap/src/test/java/org/apache/directory/server/core/avltree/ArrayMarshaller.java deleted file mode 100644 index c2d02fc9f2..0000000000 --- a/ldap/src/test/java/org/apache/directory/server/core/avltree/ArrayMarshaller.java +++ /dev/null @@ -1,173 +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.apache.directory.server.core.avltree; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Comparator; - -import org.apache.directory.shared.ldap.util.StringTools; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Class to serialize the Array data. - * - * @author Apache Directory Project - * @version $Rev$, $Date$ - */ -@SuppressWarnings("unchecked") -public class ArrayMarshaller implements Marshaller> { - - /** static logger */ - private static final Logger LOG = LoggerFactory.getLogger(ArrayMarshaller.class); - - /** used for serialized form of an empty AvlTree */ - private static final byte[] EMPTY_TREE = new byte[1]; - - /** marshaller to be used for marshalling the keys */ - private Marshaller keyMarshaller; - - /** key Comparator for the AvlTree */ - private Comparator comparator; - - /** - * Creates a new instance of AvlTreeMarshaller with a custom key Marshaller. - * @param comparator Comparator to be used for key comparision - * @param keyMarshaller marshaller for keys - */ - public ArrayMarshaller(Comparator comparator, Marshaller keyMarshaller) { - this.comparator = comparator; - this.keyMarshaller = keyMarshaller; - } - - /** - * Creates a new instance of AvlTreeMarshaller with the default key Marshaller which - * uses Java Serialization. - * @param comparator Comparator to be used for key comparision - */ - public ArrayMarshaller(Comparator comparator) { - this.comparator = comparator; - this.keyMarshaller = DefaultMarshaller.INSTANCE; - } - - /** - * Marshals the given tree to bytes - * @param tree the tree to be marshalled - */ - public byte[] serialize(ArrayTree tree) { - if ((tree == null) || tree.isEmpty()) { - return EMPTY_TREE; - } - - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(byteStream); - byte[] data = null; - - try { - out.writeByte(0); // represents the start of an Array byte stream - out.writeInt(tree.size()); - - for (int position = 0; position < tree.size(); position++) { - E value = tree.get(position); - byte[] bytes = this.keyMarshaller.serialize(value); - - // Write the key length - out.writeInt(bytes.length); - - // Write the key if its length is not null - if (bytes.length != 0) { - out.write(bytes); - } - } - - out.flush(); - data = byteStream.toByteArray(); - - // Try to deserialize, just to see - try { - deserialize(data); - } - catch (NullPointerException npe) { - System.out.println("Bad serialization, tree : [" + StringTools.dumpBytes(data) + "]"); - throw npe; - } - - out.close(); - } - catch (IOException ex) { - ex.printStackTrace(); - } - - return data; - } - - /** - * Creates an Array from given bytes of data. - * @param data byte array to be converted into an array - */ - public ArrayTree deserialize(byte[] data) throws IOException { - try { - if ((data == null) || (data.length == 0)) { - throw new IOException("Null or empty data array is invalid."); - } - - if ((data.length == 1) && (data[0] == 0)) { - E[] array = (E[]) new Object[] {}; - ArrayTree tree = new ArrayTree(this.comparator, array); - return tree; - } - - ByteArrayInputStream bin = new ByteArrayInputStream(data); - DataInputStream din = new DataInputStream(bin); - - byte startByte = din.readByte(); - - if (startByte != 0) { - throw new IOException("wrong array serialized data format"); - } - - int size = din.readInt(); - E[] nodes = (E[]) new Object[size]; - - for (int i = 0; i < size; i++) { - // Read the object's size - int dataSize = din.readInt(); - - if (dataSize != 0) { - byte[] bytes = new byte[dataSize]; - - din.read(bytes); - E key = this.keyMarshaller.deserialize(bytes); - nodes[i] = key; - } - } - - ArrayTree arrayTree = new ArrayTree(this.comparator, nodes); - - return arrayTree; - } - catch (NullPointerException npe) { - System.out.println("Bad tree : [" + StringTools.dumpBytes(data) + "]"); - throw npe; - } - } - -} diff --git a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java index 6185ff81ca..0c10e1edd7 100644 --- a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java +++ b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java @@ -29,7 +29,6 @@ import javax.naming.directory.DirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; -import org.apache.directory.shared.ldap.util.EmptyEnumeration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -175,7 +174,7 @@ public class ActiveDirectoryLdapAuthenticationProviderTests { @Test public void noUserSearchCausesUsernameNotFound() throws Exception { given(this.ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class))) - .willReturn(new EmptyEnumeration<>()); + .willReturn(new MockNamingEnumeration(null)); this.provider.contextFactory = createContextFactoryReturning(this.ctx); assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.provider.authenticate(this.joe)); } From 09983e23494b13d1cba93d98c65a0c447720d750 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:47:30 -0600 Subject: [PATCH 149/149] Add ApacheDS Migration Steps Issue gh-13852 --- docs/modules/ROOT/pages/migration-7/ldap.adoc | 245 ++++++++++++++++++ .../ROOT/pages/servlet/appendix/faq.adoc | 35 ++- .../authentication/passwords/ldap.adoc | 5 +- 3 files changed, 281 insertions(+), 4 deletions(-) diff --git a/docs/modules/ROOT/pages/migration-7/ldap.adoc b/docs/modules/ROOT/pages/migration-7/ldap.adoc index 3bef91f9ac..03c647e5a5 100644 --- a/docs/modules/ROOT/pages/migration-7/ldap.adoc +++ b/docs/modules/ROOT/pages/migration-7/ldap.adoc @@ -9,3 +9,248 @@ Consequently, support for ApacheDS will be discontinued in version 7.0. If you are currently using ApacheDS as an embedded LDAP server, we recommend migrating to https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundId]. You can find instructions in xref:servlet/authentication/passwords/ldap.adoc#servlet-authentication-ldap-embedded[this section] that describe how to set up an embedded UnboundId LDAP server. + +To migrate, you will need to consider the following: + +1. <> +2. <> +3. <> +4. <> + +[[ldap-migrate-apacheds-unboundid-dependencies]] +=== Switch Your Dependencies + +To use UnboundID, you will at least need to remove the ApacheDS dependencies: + +[tabs] +====== +Maven:: ++ +[source,maven,role="primary"] +---- + + org.apache.directory.server + apacheds-core + 1.5.5 + runtime + + + org.apache.directory.server + apacheds-server-jndi + 1.5.5 + runtime + +---- + +Gradle:: ++ +[source,gradkle,role="secondary"] +---- +implementation("org.apache.directory.server:apacheds-server-jndi") +implementation("org.apache.directory.server:apacheds-core") +---- +====== + +and replace them with UnboundID: + +[tabs] +====== +Maven:: ++ +[source,maven,role="primary"] +---- + + com.unboundid + unboundid-ldapsdk + 7.0.3 + runtime + +---- + +Gradle:: ++ +[source,gradkle,role="secondary"] +---- +implementation("org.apache.directory.server:apacheds-server-jndi") +implementation("org.apache.directory.server:apacheds-core") +---- +====== + +If you are accepting the LDAP server defaults, this is likely all you will need to do. + +[[ldap-migrate-apacheds-unboundid-container]] +=== Change Server Declaration + +If you are declaring an ApacheDS server, then you will need to change its declaration. +Your configuration may vary somewhat from the following. +Change this: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +EmbeddedLdapServerContainer ldapContainer() { + EmbeddedLdapServerContainer container = + new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); + container.setPort(0); + return container; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun ldapContainer(): EmbeddedLdapServerContainer { + val container = + ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif") + container.setPort(0) + return container +} +---- + +Xml:: ++ +[source,xml,role="secondary"] +---- + +---- +====== + +to this: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +EmbeddedLdapServerContainer ldapContainer() { + EmbeddedLdapServerContainer container = + new UnboundIdContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); + container.setPort(0); + return container; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun ldapContainer(): EmbeddedLdapServerContainer { + val container = + UnboundIdContainer("dc=springframework,dc=org", "classpath:test-server.ldif") + container.setPort(0) + return container +} +---- + +Xml:: ++ +[source,xml,role="secondary"] +---- + +---- +====== + + +[[ldap-migrate-apacheds-unboundid-password-encoding]] +=== Configure Password Encoding + +Apache Directory Server supports binding with SHA-hashed passwords, but UnboundID does not. + +If you run into trouble with binding users with SHA-hashed passwords, move to Spring Security's `PasswordComparisonAuthenticator` by providing a password encoder to the authentication provider: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) { + LdapPasswordComparisonAuthenticationManagerFactory factory = + new LdapPasswordComparisonAuthenticationManagerFactory( + contextSource, new LdapShaPasswordEncoder()); + // ... + return factory.createAuthenticationManager(); +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun ldapAuthenticationManager(val contextSource: BaseLdapPathContextSource): AuthenticationManager { + val factory = LdapPasswordComparisonAuthenticationManagerFactory( + contextSource, LdapShaPasswordEncoder()) + // ... + return factory.createAuthenticationManager() +} +---- + +Xml:: ++ +[source,xml,role="secondary"] +---- + + + + + + + + +---- +====== + +[WARN] +==== +Hashing passwords with `+{SHA}+` is not recommended. +Please migrate to BCrypt, SCrypt, or Argon2 as soon as possible. +You can use the same approach above to provide the corresponding password encoder. +==== + +[[ldap-migrate-apacheds-unboundid-password-hiding]] +=== Configure Password Hiding + +ApacheDS is configured by Spring Security to hide the `userPassword` attribute from search results unless explicitly queried. +UnboundID does not support this. + +You can achieve this behavior with a custom `InMemoryOperationInterceptor` like the following: + +[source,java] +---- +static class PasswordRemovingOperationInterceptor + extends InMemoryOperationInterceptor { + + @Override + public void processSearchEntry(InMemoryInterceptedSearchEntry entry) { + if (!entry.getRequest().getAttributeList().contains("userPassword")) { + if (entry.getSearchEntry().getAttribute("userPassword") != null) { + Entry old = entry.getSearchEntry(); + Collection attributes = old.getAttributes().stream() + .filter(attribute -> + !"userPassword".equals(attribute.getName())) + .collect(Collectors.toList()); + Entry withoutPassword = new Entry(old.getDN(), attributes); + entry.setSearchEntry(withoutPassword); + } + } + } +} +---- + +[NOTE] +==== +It is better to secure passwords by hashing them and by using queries that identify the specific columns that you need. +==== + +`UnboundIdContainer` does not currently have a way to register a custom `InMemoryOperationInterceptor`, but you can either copy the contents of `UnboundIdContainer` or use Spring LDAP Test's `EmbeddedLdapServer` builder in order to provide this interceptor and confirm your application's readiness. diff --git a/docs/modules/ROOT/pages/servlet/appendix/faq.adoc b/docs/modules/ROOT/pages/servlet/appendix/faq.adoc index d987b67642..af7f0a2a03 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/faq.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/faq.adoc @@ -363,7 +363,7 @@ This section addresses common Spring Security architecture questions: . <> . <> . <> -. <> +. <> . <> @@ -412,9 +412,42 @@ The reference manual also includes <> that lists If you build your project with Maven, adding the appropriate Spring Security modules as dependencies to your `pom.xml` file automatically pulls in the core jars that the framework requires. Any that are marked as "`optional`" in the Spring Security `pom.xml` files have to be added to your own `pom.xml` file if you need them. +[[appendix-faq-unboundid-deps]] +=== What dependences are needed to run an embedded UnboundID LDAP server? + +You need to add the following dependency to your project: + +[tabs] +====== +Maven:: ++ +[source,maven,role="primary"] +---- + + com.unboundid + unboundid-ldapsdk + 7.0.1 + runtime + +---- + +Gradle:: ++ +[source,gradle,role="secondary"] +---- +implementation 'com.unboundid:unboundid-ldapsdk:7.0.1' +---- +====== + [[appendix-faq-apacheds-deps]] === What dependencies are needed to run an embedded ApacheDS LDAP server? +[NOTE] +==== +Spring Security 7 removes support for Apache DS. +Please use <> instead. +==== + If you use Maven, you need to add the following to your `pom.xml` file dependencies: [source] diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc index de64a1d448..d1d59ee013 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc @@ -225,9 +225,8 @@ fun ldapContainer(): UnboundIdContainer { [NOTE] ==== -Spring Security uses ApacheDS 1.x, which is no longer maintained. -Unfortunately, ApacheDS 2.x has only released milestone versions with no stable release. -Once a stable release of ApacheDS 2.x is available, we will consider updating. +Spring Security 7 removes support for Apache DS. +Please use <> instead. ==== If you wish to use https://directory.apache.org/apacheds/[Apache DS], specify the following dependencies: