mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-01 09:42:13 +00:00
Use original query string to verify signature
Closes gh-11235
This commit is contained in:
parent
5adb6e25a3
commit
b51c71c3b3
@ -22,6 +22,9 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@ -267,6 +270,33 @@ public class Saml2LogoutConfigurerTests {
|
|||||||
verify(getBean(LogoutHandler.class)).logout(any(), any(), any());
|
verify(getBean(LogoutHandler.class)).logout(any(), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-11235
|
||||||
|
@Test
|
||||||
|
public void saml2LogoutRequestWhenLowercaseEncodingThenLogsOutAndSendsLogoutResponse() throws Exception {
|
||||||
|
this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
|
||||||
|
String apLogoutRequest = "nZFNa4QwEIb/iuQeP6K7dYO6FKQg2B622x56G3WwgiY2E8v239fqCksPPfSWIXmfNw+THC9D73yi\r\n"
|
||||||
|
+ "oU6rlAWuzxxUtW461abs5fzAY3bMEoKhF6Msdasne8KPCck6c1KRXK9SNhklNVBHUsGAJG0tn+8f\r\n"
|
||||||
|
+ "SylcX45GW13rnjn5HOwU2KXt3dqRpOeZ0cULDGOPrjat1y8t3gL2zFrGnCJPWXkKcR8KCHY8xmrP\r\n"
|
||||||
|
+ "Iz868OpOVLwO4wohggagmd8STVgosqBsyoQvBPd3XITnIJaRL8PYjcThjTmvm/f8SXa1lEvY3Nr9\r\n"
|
||||||
|
+ "LQdEaH6EWAYjR2U7+8W7JvFucRv8aY4X+b/g03zaoCsmu46/FpN9Aw==";
|
||||||
|
String apLogoutRequestRelayState = "d118dbd5-3853-4268-b3e5-c40fc033fa2f";
|
||||||
|
String apLogoutRequestSignature = "VZ7rWa5u3hIX60fAQs/gBQZWDP2BAIlCMMrNrTHafoKKj0uXWnuITYLuL8NdsWmyQN0+fqWW4X05+BqiLpL80jHLmQR5RVqqL1EtVv1SpPUna938lgz2sOliuYmfQNj4Bmd+Z5G1K6QhbVrtfb7TQHURjUafzfRm8+jGz3dPjVBrn/rD/umfGoSn6RuWngugcMNL4U0A+JcEh1NSfSYNVz7y+MqlW1UhX2kF86rm97ERCrxay7Gh/bI2f3fJPJ1r+EyLjzrDUkqw5cva3rVlFgEQouMVu35lUJn7SFompW8oTxkI23oc/t+AGZqaBupNITNdjyGCBpfukZ69EZrj8g==";
|
||||||
|
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user",
|
||||||
|
Collections.emptyMap());
|
||||||
|
principal.setRelyingPartyRegistrationId("get");
|
||||||
|
Saml2Authentication user = new Saml2Authentication(principal, "response",
|
||||||
|
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||||
|
MvcResult result = this.mvc
|
||||||
|
.perform(get("/logout/saml2/slo").param("SAMLRequest", apLogoutRequest)
|
||||||
|
.param("RelayState", apLogoutRequestRelayState).param("SigAlg", this.apLogoutRequestSigAlg)
|
||||||
|
.param("Signature", apLogoutRequestSignature)
|
||||||
|
.with(new SamlQueryStringRequestPostProcessor(true)).with(authentication(user)))
|
||||||
|
.andExpect(status().isFound()).andReturn();
|
||||||
|
String location = result.getResponse().getHeader("Location");
|
||||||
|
assertThat(location).startsWith("https://ap.example.org/logout/saml2/response");
|
||||||
|
verify(getBean(LogoutHandler.class)).logout(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saml2LogoutRequestWhenNoRegistrationThen400() throws Exception {
|
public void saml2LogoutRequestWhenNoRegistrationThen400() throws Exception {
|
||||||
this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
|
this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
|
||||||
@ -612,6 +642,26 @@ public class Saml2LogoutConfigurerTests {
|
|||||||
|
|
||||||
static class SamlQueryStringRequestPostProcessor implements RequestPostProcessor {
|
static class SamlQueryStringRequestPostProcessor implements RequestPostProcessor {
|
||||||
|
|
||||||
|
private Function<String, String> urlEncodingPostProcessor = Function.identity();
|
||||||
|
|
||||||
|
SamlQueryStringRequestPostProcessor() {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SamlQueryStringRequestPostProcessor(boolean lowercased) {
|
||||||
|
if (lowercased) {
|
||||||
|
Pattern encoding = Pattern.compile("%\\d[A-Fa-f]");
|
||||||
|
this.urlEncodingPostProcessor = (encoded) -> {
|
||||||
|
Matcher m = encoding.matcher(encoded);
|
||||||
|
while (m.find()) {
|
||||||
|
String found = m.group(0);
|
||||||
|
encoded = encoded.replace(found, found.toLowerCase());
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
|
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
|
||||||
UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
|
UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
|
||||||
@ -619,7 +669,8 @@ public class Saml2LogoutConfigurerTests {
|
|||||||
builder.queryParam(entries.getKey(),
|
builder.queryParam(entries.getKey(),
|
||||||
UriUtils.encode(entries.getValue()[0], StandardCharsets.ISO_8859_1));
|
UriUtils.encode(entries.getValue()[0], StandardCharsets.ISO_8859_1));
|
||||||
}
|
}
|
||||||
request.setQueryString(builder.build(true).toUriString().substring(1));
|
String queryString = this.urlEncodingPostProcessor.apply(builder.build(true).toUriString().substring(1));
|
||||||
|
request.setQueryString(queryString);
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,9 +17,11 @@
|
|||||||
package org.springframework.security.saml2.jackson2;
|
package org.springframework.security.saml2.jackson2;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
@ -46,6 +48,9 @@ import org.springframework.security.saml2.provider.service.registration.Saml2Mes
|
|||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
class Saml2LogoutRequestMixin {
|
class Saml2LogoutRequestMixin {
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
Function<Map<String, String>, String> encoder;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
Saml2LogoutRequestMixin(@JsonProperty("location") String location,
|
Saml2LogoutRequestMixin(@JsonProperty("location") String location,
|
||||||
@JsonProperty("relayState") Saml2MessageBinding relayState,
|
@JsonProperty("relayState") Saml2MessageBinding relayState,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -50,7 +50,7 @@ import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
|||||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||||
import org.springframework.web.util.UriUtils;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility methods for verifying SAML component signatures with OpenSAML
|
* Utility methods for verifying SAML component signatures with OpenSAML
|
||||||
@ -191,8 +191,9 @@ final class OpenSamlVerificationUtils {
|
|||||||
else {
|
else {
|
||||||
this.signature = null;
|
this.signature = null;
|
||||||
}
|
}
|
||||||
this.content = content(request.getSamlRequest(), Saml2ParameterNames.SAML_REQUEST,
|
this.content = UriComponentsBuilder.newInstance().query(request.getParametersQuery())
|
||||||
request.getRelayState(), request.getParameter(Saml2ParameterNames.SIG_ALG));
|
.replaceQueryParam(Saml2ParameterNames.SIGNATURE).build(true).toUriString().substring(1)
|
||||||
|
.getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
RedirectSignature(Saml2LogoutResponse response) {
|
RedirectSignature(Saml2LogoutResponse response) {
|
||||||
@ -203,22 +204,9 @@ final class OpenSamlVerificationUtils {
|
|||||||
else {
|
else {
|
||||||
this.signature = null;
|
this.signature = null;
|
||||||
}
|
}
|
||||||
this.content = content(response.getSamlResponse(), Saml2ParameterNames.SAML_RESPONSE,
|
this.content = UriComponentsBuilder.newInstance().query(response.getParametersQuery())
|
||||||
response.getRelayState(), response.getParameter(Saml2ParameterNames.SIG_ALG));
|
.replaceQueryParam(Saml2ParameterNames.SIGNATURE).build(true).toUriString().substring(1)
|
||||||
}
|
.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
static byte[] content(String samlObject, String objectParameterName, String relayState, String algorithm) {
|
|
||||||
if (relayState != null) {
|
|
||||||
return String.format("%s=%s&%s=%s&%s=%s", objectParameterName,
|
|
||||||
UriUtils.encode(samlObject, StandardCharsets.ISO_8859_1), Saml2ParameterNames.RELAY_STATE,
|
|
||||||
UriUtils.encode(relayState, StandardCharsets.ISO_8859_1), Saml2ParameterNames.SIG_ALG,
|
|
||||||
UriUtils.encode(algorithm, StandardCharsets.ISO_8859_1)).getBytes(StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return String.format("%s=%s&%s=%s", objectParameterName,
|
|
||||||
UriUtils.encode(samlObject, StandardCharsets.ISO_8859_1), Saml2ParameterNames.SIG_ALG,
|
|
||||||
UriUtils.encode(algorithm, StandardCharsets.ISO_8859_1)).getBytes(StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] getContent() {
|
byte[] getContent() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2020 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,15 +17,19 @@
|
|||||||
package org.springframework.security.saml2.provider.service.authentication.logout;
|
package org.springframework.security.saml2.provider.service.authentication.logout;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
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.Saml2MessageBinding;
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver;
|
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
import org.springframework.web.util.UriUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that represents a signed and serialized SAML 2.0 Logout Request
|
* A class that represents a signed and serialized SAML 2.0 Logout Request
|
||||||
@ -35,6 +39,17 @@ import org.springframework.security.saml2.provider.service.web.authentication.lo
|
|||||||
*/
|
*/
|
||||||
public final class Saml2LogoutRequest implements Serializable {
|
public final class Saml2LogoutRequest implements Serializable {
|
||||||
|
|
||||||
|
private static final Function<Map<String, String>, String> DEFAULT_ENCODER = (params) -> {
|
||||||
|
if (params.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
|
||||||
|
for (Map.Entry<String, String> component : params.entrySet()) {
|
||||||
|
builder.queryParam(component.getKey(), UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1));
|
||||||
|
}
|
||||||
|
return builder.build(true).toString().substring(1);
|
||||||
|
};
|
||||||
|
|
||||||
private final String location;
|
private final String location;
|
||||||
|
|
||||||
private final Saml2MessageBinding binding;
|
private final Saml2MessageBinding binding;
|
||||||
@ -45,13 +60,21 @@ public final class Saml2LogoutRequest implements Serializable {
|
|||||||
|
|
||||||
private final String relyingPartyRegistrationId;
|
private final String relyingPartyRegistrationId;
|
||||||
|
|
||||||
|
private Function<Map<String, String>, String> encoder;
|
||||||
|
|
||||||
private Saml2LogoutRequest(String location, Saml2MessageBinding binding, Map<String, String> parameters, String id,
|
private Saml2LogoutRequest(String location, Saml2MessageBinding binding, Map<String, String> parameters, String id,
|
||||||
String relyingPartyRegistrationId) {
|
String relyingPartyRegistrationId) {
|
||||||
|
this(location, binding, parameters, id, relyingPartyRegistrationId, DEFAULT_ENCODER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Saml2LogoutRequest(String location, Saml2MessageBinding binding, Map<String, String> parameters, String id,
|
||||||
|
String relyingPartyRegistrationId, Function<Map<String, String>, String> encoder) {
|
||||||
this.location = location;
|
this.location = location;
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
this.parameters = Collections.unmodifiableMap(new HashMap<>(parameters));
|
this.parameters = Collections.unmodifiableMap(new LinkedHashMap<>(parameters));
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.relyingPartyRegistrationId = relyingPartyRegistrationId;
|
this.relyingPartyRegistrationId = relyingPartyRegistrationId;
|
||||||
|
this.encoder = encoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,6 +142,16 @@ public final class Saml2LogoutRequest implements Serializable {
|
|||||||
return this.parameters;
|
return this.parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an encoded query string of all parameters. Resulting query does not contain a
|
||||||
|
* leading question mark.
|
||||||
|
* @return an encoded string of all parameters
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
public String getParametersQuery() {
|
||||||
|
return this.encoder.apply(this.parameters);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The identifier for the {@link RelyingPartyRegistration} associated with this Logout
|
* The identifier for the {@link RelyingPartyRegistration} associated with this Logout
|
||||||
* Request
|
* Request
|
||||||
@ -149,7 +182,9 @@ public final class Saml2LogoutRequest implements Serializable {
|
|||||||
|
|
||||||
private Saml2MessageBinding binding;
|
private Saml2MessageBinding binding;
|
||||||
|
|
||||||
private Map<String, String> parameters = new HashMap<>();
|
private Map<String, String> parameters = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private Function<Map<String, String>, String> encoder = DEFAULT_ENCODER;
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
@ -235,13 +270,28 @@ public final class Saml2LogoutRequest implements Serializable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this strategy for converting parameters into an encoded query string. The
|
||||||
|
* resulting query does not contain a leading question mark.
|
||||||
|
*
|
||||||
|
* In the event that you already have an encoded version that you want to use, you
|
||||||
|
* can call this by doing {@code parameterEncoder((params) -> encodedValue)}.
|
||||||
|
* @param encoder the strategy to use
|
||||||
|
* @return the {@link Builder} for further configurations
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
public Builder parametersQuery(Function<Map<String, String>, String> encoder) {
|
||||||
|
this.encoder = encoder;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the {@link Saml2LogoutRequest}
|
* Build the {@link Saml2LogoutRequest}
|
||||||
* @return a constructed {@link Saml2LogoutRequest}
|
* @return a constructed {@link Saml2LogoutRequest}
|
||||||
*/
|
*/
|
||||||
public Saml2LogoutRequest build() {
|
public Saml2LogoutRequest build() {
|
||||||
return new Saml2LogoutRequest(this.location, this.binding, this.parameters, this.id,
|
return new Saml2LogoutRequest(this.location, this.binding, this.parameters, this.id,
|
||||||
this.registration.getRegistrationId());
|
this.registration.getRegistrationId(), this.encoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,15 +16,19 @@
|
|||||||
|
|
||||||
package org.springframework.security.saml2.provider.service.authentication.logout;
|
package org.springframework.security.saml2.provider.service.authentication.logout;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
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.Saml2MessageBinding;
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver;
|
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
import org.springframework.web.util.UriUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that represents a signed and serialized SAML 2.0 Logout Response
|
* A class that represents a signed and serialized SAML 2.0 Logout Response
|
||||||
@ -34,16 +38,31 @@ import org.springframework.security.saml2.provider.service.web.authentication.lo
|
|||||||
*/
|
*/
|
||||||
public final class Saml2LogoutResponse {
|
public final class Saml2LogoutResponse {
|
||||||
|
|
||||||
|
private static final Function<Map<String, String>, String> DEFAULT_ENCODER = (params) -> {
|
||||||
|
if (params.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
|
||||||
|
for (Map.Entry<String, String> component : params.entrySet()) {
|
||||||
|
builder.queryParam(component.getKey(), UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1));
|
||||||
|
}
|
||||||
|
return builder.build(true).toString().substring(1);
|
||||||
|
};
|
||||||
|
|
||||||
private final String location;
|
private final String location;
|
||||||
|
|
||||||
private final Saml2MessageBinding binding;
|
private final Saml2MessageBinding binding;
|
||||||
|
|
||||||
private final Map<String, String> parameters;
|
private final Map<String, String> parameters;
|
||||||
|
|
||||||
private Saml2LogoutResponse(String location, Saml2MessageBinding binding, Map<String, String> parameters) {
|
private final Function<Map<String, String>, String> encoder;
|
||||||
|
|
||||||
|
private Saml2LogoutResponse(String location, Saml2MessageBinding binding, Map<String, String> parameters,
|
||||||
|
Function<Map<String, String>, String> encoder) {
|
||||||
this.location = location;
|
this.location = location;
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
this.parameters = Collections.unmodifiableMap(new HashMap<>(parameters));
|
this.parameters = Collections.unmodifiableMap(new LinkedHashMap<>(parameters));
|
||||||
|
this.encoder = encoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,6 +122,16 @@ public final class Saml2LogoutResponse {
|
|||||||
return this.parameters;
|
return this.parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an encoded query string of all parameters. Resulting query does not contain a
|
||||||
|
* leading question mark.
|
||||||
|
* @return an encoded string of all parameters
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
public String getParametersQuery() {
|
||||||
|
return this.encoder.apply(this.parameters);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link Builder} instance from this {@link RelyingPartyRegistration}
|
* Create a {@link Builder} instance from this {@link RelyingPartyRegistration}
|
||||||
*
|
*
|
||||||
@ -122,7 +151,9 @@ public final class Saml2LogoutResponse {
|
|||||||
|
|
||||||
private Saml2MessageBinding binding;
|
private Saml2MessageBinding binding;
|
||||||
|
|
||||||
private Map<String, String> parameters = new HashMap<>();
|
private Map<String, String> parameters = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private Function<Map<String, String>, String> encoder = DEFAULT_ENCODER;
|
||||||
|
|
||||||
private Builder(RelyingPartyRegistration registration) {
|
private Builder(RelyingPartyRegistration registration) {
|
||||||
this.location = registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation();
|
this.location = registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation();
|
||||||
@ -195,12 +226,27 @@ public final class Saml2LogoutResponse {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this strategy for converting parameters into an encoded query string. The
|
||||||
|
* resulting query does not contain a leading question mark.
|
||||||
|
*
|
||||||
|
* In the event that you already have an encoded version that you want to use, you
|
||||||
|
* can call this by doing {@code parameterEncoder((params) -> encodedValue)}.
|
||||||
|
* @param encoder the strategy to use
|
||||||
|
* @return the {@link Saml2LogoutRequest.Builder} for further configurations
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
public Builder parametersQuery(Function<Map<String, String>, String> encoder) {
|
||||||
|
this.encoder = encoder;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the {@link Saml2LogoutResponse}
|
* Build the {@link Saml2LogoutResponse}
|
||||||
* @return a constructed {@link Saml2LogoutResponse}
|
* @return a constructed {@link Saml2LogoutResponse}
|
||||||
*/
|
*/
|
||||||
public Saml2LogoutResponse build() {
|
public Saml2LogoutResponse build() {
|
||||||
return new Saml2LogoutResponse(this.location, this.binding, this.parameters);
|
return new Saml2LogoutResponse(this.location, this.binding, this.parameters, this.encoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,8 +17,6 @@
|
|||||||
package org.springframework.security.saml2.provider.service.web.authentication.logout;
|
package org.springframework.security.saml2.provider.service.web.authentication.logout;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
@ -53,7 +51,6 @@ import org.springframework.util.StringUtils;
|
|||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
import org.springframework.web.util.HtmlUtils;
|
import org.springframework.web.util.HtmlUtils;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
import org.springframework.web.util.UriUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A filter for handling logout requests in the form of a <saml2:LogoutRequest> sent
|
* A filter for handling logout requests in the form of a <saml2:LogoutRequest> sent
|
||||||
@ -141,7 +138,7 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
|
|||||||
request.getParameter(Saml2ParameterNames.SIG_ALG)))
|
request.getParameter(Saml2ParameterNames.SIG_ALG)))
|
||||||
.parameters((params) -> params.put(Saml2ParameterNames.SIGNATURE,
|
.parameters((params) -> params.put(Saml2ParameterNames.SIGNATURE,
|
||||||
request.getParameter(Saml2ParameterNames.SIGNATURE)))
|
request.getParameter(Saml2ParameterNames.SIGNATURE)))
|
||||||
.build();
|
.parametersQuery((params) -> request.getQueryString()).build();
|
||||||
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(logoutRequest,
|
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(logoutRequest,
|
||||||
registration, authentication);
|
registration, authentication);
|
||||||
Saml2LogoutValidatorResult result = this.logoutRequestValidator.validate(parameters);
|
Saml2LogoutValidatorResult result = this.logoutRequestValidator.validate(parameters);
|
||||||
@ -192,22 +189,11 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
|
|||||||
private void doRedirect(HttpServletRequest request, HttpServletResponse response,
|
private void doRedirect(HttpServletRequest request, HttpServletResponse response,
|
||||||
Saml2LogoutResponse logoutResponse) throws IOException {
|
Saml2LogoutResponse logoutResponse) throws IOException {
|
||||||
String location = logoutResponse.getResponseLocation();
|
String location = logoutResponse.getResponseLocation();
|
||||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(location);
|
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(location)
|
||||||
addParameter(Saml2ParameterNames.SAML_RESPONSE, logoutResponse::getParameter, uriBuilder);
|
.query(logoutResponse.getParametersQuery());
|
||||||
addParameter(Saml2ParameterNames.RELAY_STATE, logoutResponse::getParameter, uriBuilder);
|
|
||||||
addParameter(Saml2ParameterNames.SIG_ALG, logoutResponse::getParameter, uriBuilder);
|
|
||||||
addParameter(Saml2ParameterNames.SIGNATURE, logoutResponse::getParameter, uriBuilder);
|
|
||||||
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
|
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addParameter(String name, Function<String, String> parameters, UriComponentsBuilder builder) {
|
|
||||||
Assert.hasText(name, "name cannot be empty or null");
|
|
||||||
if (StringUtils.hasText(parameters.apply(name))) {
|
|
||||||
builder.queryParam(UriUtils.encode(name, StandardCharsets.ISO_8859_1),
|
|
||||||
UriUtils.encode(parameters.apply(name), StandardCharsets.ISO_8859_1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doPost(HttpServletResponse response, Saml2LogoutResponse logoutResponse) throws IOException {
|
private void doPost(HttpServletResponse response, Saml2LogoutResponse logoutResponse) throws IOException {
|
||||||
String location = logoutResponse.getResponseLocation();
|
String location = logoutResponse.getResponseLocation();
|
||||||
String saml = logoutResponse.getSamlResponse();
|
String saml = logoutResponse.getSamlResponse();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -141,7 +141,7 @@ public final class Saml2LogoutResponseFilter extends OncePerRequestFilter {
|
|||||||
request.getParameter(Saml2ParameterNames.SIG_ALG)))
|
request.getParameter(Saml2ParameterNames.SIG_ALG)))
|
||||||
.parameters((params) -> params.put(Saml2ParameterNames.SIGNATURE,
|
.parameters((params) -> params.put(Saml2ParameterNames.SIGNATURE,
|
||||||
request.getParameter(Saml2ParameterNames.SIGNATURE)))
|
request.getParameter(Saml2ParameterNames.SIGNATURE)))
|
||||||
.build();
|
.parametersQuery((params) -> request.getQueryString()).build();
|
||||||
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(logoutResponse,
|
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(logoutResponse,
|
||||||
logoutRequest, registration);
|
logoutRequest, registration);
|
||||||
Saml2LogoutValidatorResult result = this.logoutResponseValidator.validate(parameters);
|
Saml2LogoutValidatorResult result = this.logoutResponseValidator.validate(parameters);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,8 +17,6 @@
|
|||||||
package org.springframework.security.saml2.provider.service.web.authentication.logout;
|
package org.springframework.security.saml2.provider.service.web.authentication.logout;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@ -28,7 +26,6 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest;
|
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest;
|
||||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||||
@ -38,7 +35,6 @@ import org.springframework.util.Assert;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.util.HtmlUtils;
|
import org.springframework.web.util.HtmlUtils;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
import org.springframework.web.util.UriUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A success handler for issuing a SAML 2.0 Logout Request to the the SAML 2.0 Asserting
|
* A success handler for issuing a SAML 2.0 Logout Request to the the SAML 2.0 Asserting
|
||||||
@ -105,22 +101,11 @@ public final class Saml2RelyingPartyInitiatedLogoutSuccessHandler implements Log
|
|||||||
private void doRedirect(HttpServletRequest request, HttpServletResponse response, Saml2LogoutRequest logoutRequest)
|
private void doRedirect(HttpServletRequest request, HttpServletResponse response, Saml2LogoutRequest logoutRequest)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String location = logoutRequest.getLocation();
|
String location = logoutRequest.getLocation();
|
||||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(location);
|
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(location)
|
||||||
addParameter(Saml2ParameterNames.SAML_REQUEST, logoutRequest::getParameter, uriBuilder);
|
.query(logoutRequest.getParametersQuery());
|
||||||
addParameter(Saml2ParameterNames.RELAY_STATE, logoutRequest::getParameter, uriBuilder);
|
|
||||||
addParameter(Saml2ParameterNames.SIG_ALG, logoutRequest::getParameter, uriBuilder);
|
|
||||||
addParameter(Saml2ParameterNames.SIGNATURE, logoutRequest::getParameter, uriBuilder);
|
|
||||||
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
|
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addParameter(String name, Function<String, String> parameters, UriComponentsBuilder builder) {
|
|
||||||
Assert.hasText(name, "name cannot be empty or null");
|
|
||||||
if (StringUtils.hasText(parameters.apply(name))) {
|
|
||||||
builder.queryParam(UriUtils.encode(name, StandardCharsets.ISO_8859_1),
|
|
||||||
UriUtils.encode(parameters.apply(name), StandardCharsets.ISO_8859_1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doPost(HttpServletResponse response, Saml2LogoutRequest logoutRequest) throws IOException {
|
private void doPost(HttpServletResponse response, Saml2LogoutRequest logoutRequest) throws IOException {
|
||||||
String location = logoutRequest.getLocation();
|
String location = logoutRequest.getLocation();
|
||||||
String saml = logoutRequest.getSamlRequest();
|
String saml = logoutRequest.getSamlRequest();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user