Skip to content

Commit a3e09fa

Browse files
committed
Correct signature handling for SAML2 AuthNRequest
Implements the following bindings for AuthNRequest - REDIRECT - POST (future PR) Has been tested with - Keycloak - SSOCircle - Okta - SimpleSAMLPhp Fixes gh-7711
1 parent f9b783b commit a3e09fa

21 files changed

+1290
-241
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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 authenticationRequestUri;
41+
42+
/**
43+
* Mandatory constructor for the {@link AbstractSaml2AuthenticationRequest}
44+
* @param samlRequest - the SAMLRequest XML data, SAML encoded, cannot be empty or null
45+
* @param relayState - RelayState value that accompanies the request, may be null
46+
* @param authenticationRequestUri - The authenticationRequestUri, a URL, where to send the XML message, cannot be empty or null
47+
*/
48+
AbstractSaml2AuthenticationRequest(
49+
String samlRequest,
50+
String relayState,
51+
String authenticationRequestUri) {
52+
Assert.hasText(samlRequest, "samlRequest cannot be null or empty");
53+
Assert.hasText(authenticationRequestUri, "authenticationRequestUri cannot be null or empty");
54+
this.authenticationRequestUri = authenticationRequestUri;
55+
this.samlRequest = samlRequest;
56+
this.relayState = relayState;
57+
}
58+
59+
/**
60+
* Returns the AuthNRequest XML value to be sent. This value is already encoded for transport.
61+
* If {@link #getBinding()} is {@link Saml2MessageBinding#REDIRECT} the value is deflated and SAML encoded.
62+
* If {@link #getBinding()} is {@link Saml2MessageBinding#POST} the value is SAML encoded.
63+
* @return the SAMLRequest parameter value
64+
*/
65+
public String getSamlRequest() {
66+
return this.samlRequest;
67+
}
68+
69+
/**
70+
* Returns the RelayState value, if present in the parameters
71+
* @return the RelayState value, or null if not available
72+
*/
73+
public String getRelayState() {
74+
return this.relayState;
75+
}
76+
77+
/**
78+
* Returns the URI endpoint that this AuthNRequest should be sent to.
79+
* @return the URI endpoint for this message
80+
*/
81+
public String getAuthenticationRequestUri() {
82+
return this.authenticationRequestUri;
83+
}
84+
85+
/**
86+
* Returns the binding this AuthNRequest will be sent and
87+
* encoded with. If {@link Saml2MessageBinding#REDIRECT} is used, the DEFLATE encoding will be automatically applied.
88+
* @return the binding this message will be sent with.
89+
*/
90+
public abstract Saml2MessageBinding getBinding();
91+
92+
/**
93+
* A builder for {@link AbstractSaml2AuthenticationRequest} and its subclasses.
94+
*/
95+
static class Builder<T extends Builder<T>> {
96+
String authenticationRequestUri;
97+
String samlRequest;
98+
String relayState;
99+
100+
protected Builder() {
101+
}
102+
103+
/**
104+
* Casting the return as the generic subtype, when returning itself
105+
* @return this object
106+
*/
107+
@SuppressWarnings("unchecked")
108+
protected final T _this() {
109+
return (T) this;
110+
}
111+
112+
113+
/**
114+
* Sets the {@code RelayState} parameter that will accompany this AuthNRequest
115+
*
116+
* @param relayState the relay state value, unencoded. if null or empty, the parameter will be removed from the
117+
* map.
118+
* @return this object
119+
*/
120+
public T relayState(String relayState) {
121+
this.relayState = relayState;
122+
return _this();
123+
}
124+
125+
/**
126+
* Sets the {@code SAMLRequest} parameter that will accompany this AuthNRequest
127+
*
128+
* @param samlRequest the SAMLRequest parameter.
129+
* @return this object
130+
*/
131+
public T samlRequest(String samlRequest) {
132+
this.samlRequest = samlRequest;
133+
return _this();
134+
}
135+
136+
/**
137+
* Sets the {@code authenticationRequestUri}, a URL that will receive the AuthNRequest message
138+
*
139+
* @param authenticationRequestUri the relay state value, unencoded.
140+
* @return this object
141+
*/
142+
public T authenticationRequestUri(String authenticationRequestUri) {
143+
this.authenticationRequestUri = authenticationRequestUri;
144+
return _this();
145+
}
146+
}
147+
148+
}

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

+62-12
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,26 +43,65 @@ 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 context, 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()));
4693
auth.setForceAuthn(Boolean.FALSE);
4794
auth.setIsPassive(Boolean.FALSE);
4895
auth.setProtocolBinding(protocolBinding);
4996
Issuer issuer = this.saml.buildSAMLObject(Issuer.class);
50-
issuer.setValue(request.getIssuer());
97+
issuer.setValue(context.getIssuer());
5198
auth.setIssuer(issuer);
52-
auth.setDestination(request.getDestination());
53-
auth.setAssertionConsumerServiceURL(request.getAssertionConsumerServiceUrl());
99+
auth.setDestination(context.getDestination());
100+
auth.setAssertionConsumerServiceURL(context.getAssertionConsumerServiceUrl());
54101
return this.saml.toXml(
55102
auth,
56-
request.getCredentials(),
57-
request.getIssuer()
103+
credentials,
104+
context.getIssuer()
58105
);
59106
}
60107

@@ -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)