Skip to content

Commit 0c38887

Browse files
committed
Simple fix for spring-projectsgh-7711 that changes the way the AuthNRequest is signed
Has been tested with - Keycloak - SSOCircle - Okta - SimpleSAMLPhp Further configuration options (POST vs REDIRECT) that build on top of this PR can be found in: spring-projects#7759 [fixes spring-projects#7711]
1 parent eb7df64 commit 0c38887

21 files changed

+1315
-237
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.saml2.provider.service.authentication;
18+
19+
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
20+
import org.springframework.util.Assert;
21+
22+
import java.nio.charset.Charset;
23+
24+
/**
25+
* Data holder for {@code AuthNRequest} parameters to be sent using either the
26+
* {@link Saml2MessageBinding#POST} or {@link Saml2MessageBinding#REDIRECT} binding.
27+
* Data will be encoded and possibly deflated, but will not be escaped for transport,
28+
* ie URL encoded, {@link org.springframework.web.util.UriUtils#encode(String, Charset)}
29+
* or HTML encoded, {@link org.springframework.web.util.HtmlUtils#htmlEscape(String)}.
30+
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031)
31+
*
32+
* @see Saml2AuthenticationRequestFactory#createPostAuthenticationRequest(Saml2AuthenticationRequestContext)
33+
* @see Saml2AuthenticationRequestFactory#createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)
34+
* @since 5.3
35+
*/
36+
abstract class AbstractSaml2AuthenticationRequest {
37+
38+
private final String samlRequest;
39+
private final String relayState;
40+
private final String destination;
41+
private String issuer;
42+
43+
/**
44+
* Mandatory constructor for the {@link AbstractSaml2AuthenticationRequest}
45+
* @param issuer - typically a URL, cannot be empty or null
46+
* @param samlRequest - the SAMLRequest XML data, SAML encoded, cannot be empty or null
47+
* @param relayState - RelayState value that accompanies the request, may be null
48+
* @param destination - The destination, a URL, where to send the XML message, cannot be empty or null
49+
*/
50+
AbstractSaml2AuthenticationRequest(
51+
String issuer,
52+
String samlRequest,
53+
String relayState,
54+
String destination) {
55+
Assert.hasText(issuer, "issuer cannot be null or empty");
56+
Assert.hasText(samlRequest, "samlRequest cannot be null or empty");
57+
Assert.hasText(destination, "destination cannot be null or empty");
58+
this.issuer = issuer;
59+
this.destination = destination;
60+
this.samlRequest = samlRequest;
61+
this.relayState = relayState;
62+
}
63+
64+
/**
65+
* Returns the AuthNRequest Issuer value of this SP.
66+
* @return the AuthNRequest Issuer value
67+
*/
68+
public String getIssuer() {
69+
return this.issuer;
70+
}
71+
72+
/**
73+
* Returns the AuthNRequest XML value to be sent. This value is already encoded for transport.
74+
* If {@link #getBinding()} is {@link Saml2MessageBinding#REDIRECT} the value is deflated and SAML encoded.
75+
* If {@link #getBinding()} is {@link Saml2MessageBinding#POST} the value is SAML encoded.
76+
* @return the SAMLRequest parameter value
77+
*/
78+
public String getSamlRequest() {
79+
return this.samlRequest;
80+
}
81+
82+
/**
83+
* Returns the RelayState value, if present in the parameters
84+
* @return the RelayState value, or null if not available
85+
*/
86+
public String getRelayState() {
87+
return this.relayState;
88+
}
89+
90+
/**
91+
* Returns the destination that this AuthNRequest should be sent to and
92+
* the value stored inside the AuthNRequest XML
93+
* @return the destination URL for this message
94+
*/
95+
public String getDestination() {
96+
return this.destination;
97+
}
98+
99+
/**
100+
* Returns the binding this AuthNRequest will be sent and
101+
* encoded with. If {@link Saml2MessageBinding#REDIRECT} is used, the DEFLATE encoding will be automatically applied.
102+
* @return the binding this message will be sent with.
103+
*/
104+
public abstract Saml2MessageBinding getBinding();
105+
106+
/**
107+
* A builder for {@link AbstractSaml2AuthenticationRequest} and its subclasses.
108+
*/
109+
static class Builder<T extends Builder<T>> {
110+
String destination;
111+
String samlRequest;
112+
String relayState;
113+
String issuer;
114+
115+
protected Builder() {
116+
}
117+
118+
/**
119+
* Casting the return as the generic subtype, when returning itself
120+
* @return this object
121+
*/
122+
@SuppressWarnings("unchecked")
123+
protected final T _this() {
124+
return (T) this;
125+
}
126+
127+
128+
/**
129+
* Sets the {@code RelayState} parameter that will accompany this AuthNRequest
130+
*
131+
* @param relayState the relay state value, unencoded. if null or empty, the parameter will be removed from the
132+
* map.
133+
* @return this object
134+
*/
135+
public T relayState(String relayState) {
136+
this.relayState = relayState;
137+
return _this();
138+
}
139+
140+
/**
141+
* Sets the {@code issuer} value that was used to generate the request
142+
*
143+
* @param issuer the issuer state value, unencoded.
144+
* @return this object
145+
*/
146+
public T issuer(String issuer) {
147+
this.issuer = issuer;
148+
return _this();
149+
}
150+
151+
/**
152+
* Sets the {@code SAMLRequest} parameter that will accompany this AuthNRequest
153+
*
154+
* @param samlRequest the SAMLRequest parameter.
155+
* @return this object
156+
*/
157+
public T samlRequest(String samlRequest) {
158+
this.samlRequest = samlRequest;
159+
return _this();
160+
}
161+
162+
/**
163+
* Sets the {@code destination}, a URL that will receive the AuthNRequest message
164+
*
165+
* @param destination the relay state value, unencoded. if null or empty, the parameter will be removed from the
166+
* map.
167+
* @return this object
168+
*/
169+
public T destination(String destination) {
170+
this.destination = destination;
171+
return _this();
172+
}
173+
}
174+
175+
}

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,17 +16,25 @@
1616

1717
package org.springframework.security.saml2.provider.service.authentication;
1818

19-
import org.opensaml.saml.common.xml.SAMLConstants;
20-
import org.springframework.util.Assert;
21-
2219
import org.joda.time.DateTime;
20+
import org.opensaml.saml.common.xml.SAMLConstants;
2321
import org.opensaml.saml.saml2.core.AuthnRequest;
2422
import org.opensaml.saml.saml2.core.Issuer;
23+
import org.springframework.security.saml2.credentials.Saml2X509Credential;
24+
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.Builder;
25+
import org.springframework.util.Assert;
2526

2627
import java.time.Clock;
2728
import java.time.Instant;
29+
import java.util.List;
30+
import java.util.Map;
2831
import java.util.UUID;
2932

33+
import static java.nio.charset.StandardCharsets.UTF_8;
34+
import static java.util.Collections.emptyList;
35+
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDeflate;
36+
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlEncode;
37+
3038
/**
3139
* @since 5.2
3240
*/
@@ -35,11 +43,50 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
3543
private final OpenSamlImplementation saml = OpenSamlImplementation.getInstance();
3644
private String protocolBinding = SAMLConstants.SAML2_POST_BINDING_URI;
3745

46+
@Override
47+
@Deprecated
48+
public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
49+
return createAuthenticationRequest(request, request.getCredentials());
50+
}
51+
3852
/**
3953
* {@inheritDoc}
4054
*/
4155
@Override
42-
public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
56+
public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) {
57+
String xml = createAuthenticationRequest(context, context.getRelyingPartyRegistration().getSigningCredentials());
58+
return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
59+
.samlRequest(samlEncode(xml.getBytes(UTF_8)))
60+
.build();
61+
}
62+
63+
/**
64+
* {@inheritDoc}
65+
*/
66+
@Override
67+
public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext context) {
68+
String xml = createAuthenticationRequest(context, emptyList());
69+
List<Saml2X509Credential> signingCredentials = context.getRelyingPartyRegistration().getSigningCredentials();
70+
Builder result = Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context);
71+
72+
String deflatedAndEncoded = samlEncode(samlDeflate(xml));
73+
Map<String, String> signedParams = this.saml.signQueryParameters(
74+
signingCredentials,
75+
deflatedAndEncoded,
76+
context.getRelayState()
77+
);
78+
result.samlRequest(signedParams.get("SAMLRequest"))
79+
.relayState(signedParams.get("RelayState"))
80+
.sigAlg(signedParams.get("SigAlg"))
81+
.signature(signedParams.get("Signature"));
82+
return result.build();
83+
}
84+
85+
private String createAuthenticationRequest(Saml2AuthenticationRequestContext request, List<Saml2X509Credential> credentials) {
86+
return createAuthenticationRequest(Saml2AuthenticationRequest.withAuthenticationRequestContext(request).build(), credentials);
87+
}
88+
89+
private String createAuthenticationRequest(Saml2AuthenticationRequest request, List<Saml2X509Credential> credentials) {
4390
AuthnRequest auth = this.saml.buildSAMLObject(AuthnRequest.class);
4491
auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));
4592
auth.setIssueInstant(new DateTime(this.clock.millis()));
@@ -53,7 +100,7 @@ public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
53100
auth.setAssertionConsumerServiceURL(request.getAssertionConsumerServiceUrl());
54101
return this.saml.toXml(
55102
auth,
56-
request.getCredentials(),
103+
credentials,
57104
request.getIssuer()
58105
);
59106
}
@@ -71,11 +118,14 @@ public void setClock(Clock clock) {
71118
}
72119

73120
/**
74-
* Sets the {@code protocolBinding} to use when generating authentication requests
121+
* Sets the {@code protocolBinding} to use when generating authentication requests.
75122
* Acceptable values are {@link SAMLConstants#SAML2_POST_BINDING_URI} and
76123
* {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI}
124+
* The IDP will be reading this value in the {@code AuthNRequest} to determine how to
125+
* send the Response/Assertion to the ACS URL, assertion consumer service URL.
77126
*
78-
* @param protocolBinding
127+
* @param protocolBinding either {@link SAMLConstants#SAML2_POST_BINDING_URI} or
128+
* {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI}
79129
* @throws IllegalArgumentException if the protocolBinding is not valid
80130
*/
81131
public void setProtocolBinding(String protocolBinding) {

0 commit comments

Comments
 (0)