浏览代码

Support No SingleLogoutServiceLocation

Closes gh-10674
Josh Cummings 3 年之前
父节点
当前提交
c664fbc1a3
共有 13 个文件被更改,包括 82 次插入5 次删除
  1. 3 1
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java
  2. 2 0
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java
  3. 3 0
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestResolver.java
  4. 3 0
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseResolver.java
  5. 6 0
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java
  6. 6 0
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilter.java
  7. 3 1
      saml2/saml2-service-provider/src/opensaml3Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml3LogoutRequestResolverTests.java
  8. 4 1
      saml2/saml2-service-provider/src/opensaml3Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml3LogoutResponseResolverTests.java
  9. 3 1
      saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolverTests.java
  10. 4 1
      saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java
  11. 9 0
      saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java
  12. 16 0
      saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilterTests.java
  13. 20 0
      saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilterTests.java

+ 3 - 1
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java

@@ -86,7 +86,9 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
 		spSsoDescriptor.getKeyDescriptors()
 				.addAll(buildKeys(registration.getDecryptionX509Credentials(), UsageType.ENCRYPTION));
 		spSsoDescriptor.getAssertionConsumerServices().add(buildAssertionConsumerService(registration));
-		spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration));
+		if (registration.getSingleLogoutServiceLocation() != null) {
+			spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration));
+		}
 		return spSsoDescriptor;
 	}
 

+ 2 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java

@@ -106,6 +106,8 @@ public final class RelyingPartyRegistration {
 		Assert.hasText(entityId, "entityId cannot be empty");
 		Assert.hasText(assertionConsumerServiceLocation, "assertionConsumerServiceLocation cannot be empty");
 		Assert.notNull(assertionConsumerServiceBinding, "assertionConsumerServiceBinding cannot be null");
+		Assert.isTrue(singleLogoutServiceLocation == null || singleLogoutServiceBinding != null,
+				"singleLogoutServiceBinding cannot be null when singleLogoutServiceLocation is set");
 		Assert.notNull(providerDetails, "providerDetails cannot be null");
 		Assert.notEmpty(credentials, "credentials cannot be empty");
 		for (org.springframework.security.saml2.credentials.Saml2X509Credential c : credentials) {

+ 3 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestResolver.java

@@ -111,6 +111,9 @@ final class OpenSamlLogoutRequestResolver {
 		if (registration == null) {
 			return null;
 		}
+		if (registration.getAssertingPartyDetails().getSingleLogoutServiceLocation() == null) {
+			return null;
+		}
 		LogoutRequest logoutRequest = this.logoutRequestBuilder.buildObject();
 		logoutRequest.setDestination(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation());
 		Issuer issuer = this.issuerBuilder.buildObject();

+ 3 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseResolver.java

@@ -132,6 +132,9 @@ final class OpenSamlLogoutResponseResolver {
 		if (registration == null) {
 			return null;
 		}
+		if (registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation() == null) {
+			return null;
+		}
 		String serialized = request.getParameter(Saml2ParameterNames.SAML_REQUEST);
 		byte[] b = Saml2Utils.samlDecode(serialized);
 		LogoutRequest logoutRequest = parse(inflateIfRequired(registration, b));

+ 6 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java

@@ -120,6 +120,12 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
 			response.sendError(HttpServletResponse.SC_BAD_REQUEST);
 			return;
 		}
+		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;
+		}
 		if (!isCorrectBinding(request, registration)) {
 			this.logger.trace("Did not process logout request since used incorrect binding");
 			response.sendError(HttpServletResponse.SC_UNAUTHORIZED);

+ 6 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilter.java

@@ -120,6 +120,12 @@ public final class Saml2LogoutResponseFilter extends OncePerRequestFilter {
 			response.sendError(HttpServletResponse.SC_BAD_REQUEST, error.toString());
 			return;
 		}
+		if (registration.getSingleLogoutServiceResponseLocation() == null) {
+			this.logger.trace(
+					"Did not process logout response since RelyingPartyRegistration has not been configured with a logout response endpoint");
+			response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+			return;
+		}
 		if (!isCorrectBinding(request, registration)) {
 			this.logger.trace("Did not process logout request since used incorrect binding");
 			response.sendError(HttpServletResponse.SC_UNAUTHORIZED);

+ 3 - 1
saml2/saml2-service-provider/src/opensaml3Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml3LogoutRequestResolverTests.java

@@ -47,7 +47,9 @@ public class OpenSaml3LogoutRequestResolverTests {
 				this.relyingPartyRegistrationResolver);
 		logoutRequestResolver.setParametersConsumer((parameters) -> parameters.getLogoutRequest().setID("myid"));
 		HttpServletRequest request = new MockHttpServletRequest();
-		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
+		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
+				.assertingPartyDetails((party) -> party.singleLogoutServiceLocation("https://ap.example.com/logout"))
+				.build();
 		Authentication authentication = new TestingAuthenticationToken("user", "password");
 		given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
 		Saml2LogoutRequest logoutRequest = logoutRequestResolver.resolve(request, authentication);

+ 4 - 1
saml2/saml2-service-provider/src/opensaml3Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml3LogoutResponseResolverTests.java

@@ -53,7 +53,10 @@ public class OpenSaml3LogoutResponseResolverTests {
 		Consumer<LogoutResponseParameters> parametersConsumer = mock(Consumer.class);
 		logoutResponseResolver.setParametersConsumer(parametersConsumer);
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
+		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
+				.assertingPartyDetails(
+						(party) -> party.singleLogoutServiceResponseLocation("https://ap.example.com/logout"))
+				.build();
 		Authentication authentication = new TestingAuthenticationToken("user", "password");
 		LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
 		request.setParameter(Saml2ParameterNames.SAML_REQUEST,

+ 3 - 1
saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolverTests.java

@@ -47,7 +47,9 @@ public class OpenSaml4LogoutRequestResolverTests {
 				this.relyingPartyRegistrationResolver);
 		logoutRequestResolver.setParametersConsumer((parameters) -> parameters.getLogoutRequest().setID("myid"));
 		HttpServletRequest request = new MockHttpServletRequest();
-		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
+		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
+				.assertingPartyDetails((party) -> party.singleLogoutServiceLocation("https://ap.example.com/logout"))
+				.build();
 		Authentication authentication = new TestingAuthenticationToken("user", "password");
 		given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
 		Saml2LogoutRequest logoutRequest = logoutRequestResolver.resolve(request, authentication);

+ 4 - 1
saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java

@@ -53,7 +53,10 @@ public class OpenSaml4LogoutResponseResolverTests {
 		Consumer<LogoutResponseParameters> parametersConsumer = mock(Consumer.class);
 		logoutResponseResolver.setParametersConsumer(parametersConsumer);
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
+		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
+				.assertingPartyDetails(
+						(party) -> party.singleLogoutServiceResponseLocation("https://ap.example.com/logout"))
+				.build();
 		Authentication authentication = new TestingAuthenticationToken("user", "password");
 		LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
 		request.setParameter(Saml2ParameterNames.SAML_REQUEST,

+ 9 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java

@@ -61,4 +61,13 @@ public class OpenSamlMetadataResolverTests {
 				.contains("ResponseLocation=\"https://rp.example.org/logout/saml2/response\"");
 	}
 
+	@Test
+	public void resolveWhenRelyingPartyNoLogoutThenMetadataMatches() {
+		RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full()
+				.singleLogoutServiceLocation(null).build();
+		OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
+		String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
+		assertThat(metadata).doesNotContain("ResponseLocation");
+	}
+
 }

+ 16 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilterTests.java

@@ -153,4 +153,20 @@ public class Saml2LogoutRequestFilterTests {
 		verifyNoInteractions(this.logoutHandler);
 	}
 
+	@Test
+	public void doFilterWhenNoRelyingPartyLogoutThen401() throws Exception {
+		Authentication authentication = new TestingAuthenticationToken("user", "password");
+		SecurityContextHolder.getContext().setAuthentication(authentication);
+		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo");
+		request.setServletPath("/logout/saml2/slo");
+		request.setParameter(Saml2ParameterNames.SAML_REQUEST, "request");
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().singleLogoutServiceLocation(null)
+				.build();
+		given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
+		this.logoutRequestProcessingFilter.doFilterInternal(request, response, new MockFilterChain());
+		assertThat(response.getStatus()).isEqualTo(401);
+		verifyNoInteractions(this.logoutHandler);
+	}
+
 }

+ 20 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilterTests.java

@@ -37,6 +37,7 @@ import org.springframework.security.saml2.provider.service.registration.TestRely
 import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
 import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.BDDMockito.mock;
@@ -151,4 +152,23 @@ public class Saml2LogoutResponseFilterTests {
 		verifyNoInteractions(this.logoutSuccessHandler);
 	}
 
+	@Test
+	public void doFilterWhenNoRelyingPartyLogoutThen401() throws Exception {
+		Authentication authentication = new TestingAuthenticationToken("user", "password");
+		SecurityContextHolder.getContext().setAuthentication(authentication);
+		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo");
+		request.setServletPath("/logout/saml2/slo");
+		request.setParameter(Saml2ParameterNames.SAML_RESPONSE, "response");
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().singleLogoutServiceLocation(null)
+				.singleLogoutServiceResponseLocation(null).build();
+		given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
+		Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
+				.samlRequest("request").build();
+		given(this.logoutRequestRepository.removeLogoutRequest(request, response)).willReturn(logoutRequest);
+		this.logoutResponseProcessingFilter.doFilterInternal(request, response, new MockFilterChain());
+		assertThat(response.getStatus()).isEqualTo(401);
+		verifyNoInteractions(this.logoutSuccessHandler);
+	}
+
 }