diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java index 5289f5ff72e..67bca19b443 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java @@ -19,6 +19,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.saml2.core.Saml2Error; @@ -109,6 +110,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ "No relying party registration found"); throw new Saml2AuthenticationException(saml2Error); } + setDetails(request, authentication); this.authenticationRequestRepository.removeAuthenticationRequest(request, response); return getAuthenticationManager().authenticate(authentication); } @@ -138,4 +140,11 @@ private void setAuthenticationRequestRepositoryIntoAuthenticationConverter( } } + private void setDetails(HttpServletRequest request, Authentication authentication) { + if (AbstractAuthenticationToken.class.isAssignableFrom(authentication.getClass())) { + Object details = this.authenticationDetailsSource.buildDetails(request); + ((AbstractAuthenticationToken) authentication).setDetails(details); + } + } + } diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java index d83830e5939..b9e0c1070f8 100644 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java +++ b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java @@ -446,7 +446,12 @@ public Authentication authenticate(Authentication authentication) throws Authent String serializedResponse = token.getSaml2Response(); Response response = parse(serializedResponse); process(token, response); - return this.responseAuthenticationConverter.convert(new ResponseToken(response, token)); + AbstractAuthenticationToken authenticationResponse = this.responseAuthenticationConverter + .convert(new ResponseToken(response, token)); + if (authenticationResponse != null) { + authenticationResponse.setDetails(authentication.getDetails()); + } + return authenticationResponse; } catch (Saml2AuthenticationException ex) { throw ex; diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java index 46724540fd7..c0131fa1d30 100644 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java +++ b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java @@ -352,6 +352,21 @@ public void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationExcepti .satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); } + @Test + public void authenticateWhenAuthenticationHasDetailsThenSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + assertion.getSubject().getSubjectConfirmations() + .forEach((sc) -> sc.getSubjectConfirmationData().setAddress("10.10.10.10")); + TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(response, verifying(registration())); + token.setDetails("some-details"); + Authentication authentication = this.provider.authenticate(token); + assertThat(authentication.getDetails()).isEqualTo("some-details"); + } + @Test public void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException { Response response = response(); diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java index eb63f84fb81..65ea0f07dc5 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java @@ -25,12 +25,14 @@ import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; 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.TestSaml2AuthenticationTokens; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; @@ -40,11 +42,13 @@ import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; 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.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -119,6 +123,36 @@ public void attemptAuthenticationWhenSavedAuthnRequestThenRemovesAuthnRequest() verify(authenticationRequestRepository).removeAuthenticationRequest(this.request, this.response); } + @Test + public void attemptAuthenticationAddsDetails() { + AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); + final Saml2AuthenticationToken token = TestSaml2AuthenticationTokens.token(); + given(authenticationConverter.convert(this.request)).willReturn(token); + final AuthenticationDetailsSource authenticationDetailsSource = mock(AuthenticationDetailsSource.class); + final WebAuthenticationDetails details = mock(WebAuthenticationDetails.class); + given(authenticationDetailsSource.buildDetails(this.request)).willReturn(details); + this.filter = new Saml2WebSsoAuthenticationFilter(authenticationConverter, "/some/other/path/{registrationId}"); + this.filter.setAuthenticationManager((authentication) -> null); + this.filter.setAuthenticationDetailsSource(authenticationDetailsSource); + this.request.setPathInfo("/some/other/path/idp-registration-id"); + this.filter.attemptAuthentication(this.request, this.response); + Assertions.assertEquals(details, token.getDetails()); + } + + @Test + public void attemptAuthenticationWhenAuthenticationNotAbstractAuthenticationTokenDoesNotAddDetails() { + AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); + final Authentication authenticationWithoutDetails = mock(Authentication.class); + given(authenticationConverter.convert(this.request)).willReturn(authenticationWithoutDetails); + final AuthenticationDetailsSource authenticationDetailsSource = mock(AuthenticationDetailsSource.class); + this.filter = new Saml2WebSsoAuthenticationFilter(authenticationConverter, "/some/other/path/{registrationId}"); + this.filter.setAuthenticationManager((authentication) -> null); + this.filter.setAuthenticationDetailsSource(authenticationDetailsSource); + this.request.setPathInfo("/some/other/path/idp-registration-id"); + assertThatNoException().isThrownBy(() -> this.filter.attemptAuthentication(this.request, this.response)); + verifyNoInteractions(authenticationDetailsSource); + } + @Test public void setAuthenticationRequestRepositoryWhenNullThenThrowsIllegalArgument() { assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthenticationRequestRepository(null))