Skip to content

Commit e6d40e8

Browse files
authored
Merge pull request #7477 from fhanik/feature/propagate_saml_authentication_exception
propagate saml authentication exception #7375
2 parents 7217bb5 + 22da2b4 commit e6d40e8

File tree

13 files changed

+1617
-189
lines changed

13 files changed

+1617
-189
lines changed

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

+161-82
Large diffs are not rendered by default.

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

+5-13
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,8 @@
1919
import org.springframework.util.Assert;
2020

2121
import org.joda.time.DateTime;
22-
import org.opensaml.core.xml.io.MarshallingException;
2322
import org.opensaml.saml.saml2.core.AuthnRequest;
2423
import org.opensaml.saml.saml2.core.Issuer;
25-
import org.opensaml.security.SecurityException;
26-
import org.opensaml.xmlsec.signature.support.SignatureException;
2724

2825
import java.time.Clock;
2926
import java.time.Instant;
@@ -51,16 +48,11 @@ public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
5148
issuer.setValue(request.getLocalSpEntityId());
5249
auth.setIssuer(issuer);
5350
auth.setDestination(request.getWebSsoUri());
54-
try {
55-
return this.saml.toXml(
56-
auth,
57-
request.getCredentials(),
58-
request.getLocalSpEntityId()
59-
);
60-
}
61-
catch (MarshallingException | SignatureException | SecurityException e) {
62-
throw new IllegalStateException(e);
63-
}
51+
return this.saml.toXml(
52+
auth,
53+
request.getCredentials(),
54+
request.getLocalSpEntityId()
55+
);
6456
}
6557

6658
/**

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

+47-35
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,6 @@
1515
*/
1616
package org.springframework.security.saml2.provider.service.authentication;
1717

18-
import java.io.ByteArrayInputStream;
19-
import java.nio.charset.StandardCharsets;
20-
import java.util.HashMap;
21-
import java.util.List;
22-
import java.util.Map;
23-
import javax.xml.XMLConstants;
24-
import javax.xml.namespace.QName;
25-
2618
import org.springframework.security.saml2.Saml2Exception;
2719
import org.springframework.security.saml2.credentials.Saml2X509Credential;
2820

@@ -59,6 +51,14 @@
5951
import org.w3c.dom.Document;
6052
import org.w3c.dom.Element;
6153

54+
import java.io.ByteArrayInputStream;
55+
import java.nio.charset.StandardCharsets;
56+
import java.util.HashMap;
57+
import java.util.List;
58+
import java.util.Map;
59+
import javax.xml.XMLConstants;
60+
import javax.xml.namespace.QName;
61+
6262
import static java.lang.Boolean.FALSE;
6363
import static java.lang.Boolean.TRUE;
6464
import static java.util.Arrays.asList;
@@ -165,10 +165,7 @@ <T> T buildSAMLObject(final Class<T> clazz) {
165165
QName defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null);
166166
return (T) getBuilderFactory().getBuilder(defaultElementName).buildObject(defaultElementName);
167167
}
168-
catch (IllegalAccessException e) {
169-
throw new Saml2Exception("Could not create SAML object", e);
170-
}
171-
catch (NoSuchFieldException e) {
168+
catch (NoSuchFieldException | IllegalAccessException e) {
172169
throw new Saml2Exception("Could not create SAML object", e);
173170
}
174171
}
@@ -177,6 +174,28 @@ XMLObject resolve(String xml) {
177174
return resolve(xml.getBytes(StandardCharsets.UTF_8));
178175
}
179176

177+
String toXml(XMLObject object, List<Saml2X509Credential> signingCredentials, String localSpEntityId) {
178+
if (object instanceof SignableSAMLObject && null != hasSigningCredential(signingCredentials)) {
179+
signXmlObject(
180+
(SignableSAMLObject) object,
181+
signingCredentials,
182+
localSpEntityId
183+
);
184+
}
185+
final MarshallerFactory marshallerFactory = XMLObjectProviderRegistrySupport.getMarshallerFactory();
186+
try {
187+
Element element = marshallerFactory.getMarshaller(object).marshall(object);
188+
return SerializeSupport.nodeToString(element);
189+
} catch (MarshallingException e) {
190+
throw new Saml2Exception(e);
191+
}
192+
}
193+
194+
/*
195+
* ==============================================================
196+
* PRIVATE METHODS
197+
* ==============================================================
198+
*/
180199
private XMLObject resolve(byte[] xml) {
181200
XMLObject parsed = parse(xml);
182201
if (parsed != null) {
@@ -200,18 +219,6 @@ private UnmarshallerFactory getUnmarshallerFactory() {
200219
return XMLObjectProviderRegistrySupport.getUnmarshallerFactory();
201220
}
202221

203-
String toXml(XMLObject object, List<Saml2X509Credential> signingCredentials, String localSpEntityId)
204-
throws MarshallingException, SignatureException, SecurityException {
205-
if (object instanceof SignableSAMLObject && null != hasSigningCredential(signingCredentials)) {
206-
signXmlObject(
207-
(SignableSAMLObject) object,
208-
getSigningCredential(signingCredentials, localSpEntityId)
209-
);
210-
}
211-
final MarshallerFactory marshallerFactory = XMLObjectProviderRegistrySupport.getMarshallerFactory();
212-
Element element = marshallerFactory.getMarshaller(object).marshall(object);
213-
return SerializeSupport.nodeToString(element);
214-
}
215222

216223
private Saml2X509Credential hasSigningCredential(List<Saml2X509Credential> credentials) {
217224
for (Saml2X509Credential c : credentials) {
@@ -222,29 +229,34 @@ private Saml2X509Credential hasSigningCredential(List<Saml2X509Credential> crede
222229
return null;
223230
}
224231

225-
private void signXmlObject(SignableSAMLObject object, Credential credential)
226-
throws MarshallingException, SecurityException, SignatureException {
227-
SignatureSigningParameters parameters = new SignatureSigningParameters();
228-
parameters.setSigningCredential(credential);
229-
parameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
230-
parameters.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
231-
parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
232-
SignatureSupport.signObject(object, parameters);
233-
}
234-
235232
private Credential getSigningCredential(List<Saml2X509Credential> signingCredential,
236233
String localSpEntityId
237234
) {
238235
Saml2X509Credential credential = hasSigningCredential(signingCredential);
239236
if (credential == null) {
240-
throw new IllegalArgumentException("no signing credential configured");
237+
throw new Saml2Exception("no signing credential configured");
241238
}
242239
BasicCredential cred = getBasicCredential(credential);
243240
cred.setEntityId(localSpEntityId);
244241
cred.setUsageType(UsageType.SIGNING);
245242
return cred;
246243
}
247244

245+
private void signXmlObject(SignableSAMLObject object, List<Saml2X509Credential> signingCredentials, String entityId) {
246+
SignatureSigningParameters parameters = new SignatureSigningParameters();
247+
Credential credential = getSigningCredential(signingCredentials, entityId);
248+
parameters.setSigningCredential(credential);
249+
parameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
250+
parameters.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
251+
parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
252+
try {
253+
SignatureSupport.signObject(object, parameters);
254+
} catch (MarshallingException | SignatureException | SecurityException e) {
255+
throw new Saml2Exception(e);
256+
}
257+
258+
}
259+
248260
private BasicX509Credential getBasicCredential(Saml2X509Credential credential) {
249261
return CredentialSupport.getSimpleCredential(
250262
credential.getCertificate(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2002-2019 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.core.Authentication;
20+
import org.springframework.security.core.AuthenticationException;
21+
import org.springframework.util.Assert;
22+
23+
/**
24+
* This exception is thrown for all SAML 2.0 related {@link Authentication} errors.
25+
*
26+
* <p>
27+
* There are a number of scenarios where an error may occur, for example:
28+
* <ul>
29+
* <li>The response or assertion request is missing or malformed</li>
30+
* <li>Missing or invalid subject</li>
31+
* <li>Missing or invalid signatures</li>
32+
* <li>The time period validation for the assertion fails</li>
33+
* <li>One of the assertion conditions was not met</li>
34+
* <li>Decryption failed</li>
35+
* <li>Unable to locate a subject identifier, commonly known as username</li>
36+
* </ul>
37+
*
38+
* @since 5.2
39+
*/
40+
public class Saml2AuthenticationException extends AuthenticationException {
41+
private Saml2Error error;
42+
43+
/**
44+
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
45+
*
46+
* @param error the {@link Saml2Error SAML 2.0 Error}
47+
*/
48+
public Saml2AuthenticationException(Saml2Error error) {
49+
this(error, error.getDescription());
50+
}
51+
52+
/**
53+
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
54+
*
55+
* @param error the {@link Saml2Error SAML 2.0 Error}
56+
* @param cause the root cause
57+
*/
58+
public Saml2AuthenticationException(Saml2Error error, Throwable cause) {
59+
this(error, cause.getMessage(), cause);
60+
}
61+
62+
/**
63+
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
64+
*
65+
* @param error the {@link Saml2Error SAML 2.0 Error}
66+
* @param message the detail message
67+
*/
68+
public Saml2AuthenticationException(Saml2Error error, String message) {
69+
super(message);
70+
this.setError(error);
71+
}
72+
73+
/**
74+
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
75+
*
76+
* @param error the {@link Saml2Error SAML 2.0 Error}
77+
* @param message the detail message
78+
* @param cause the root cause
79+
*/
80+
public Saml2AuthenticationException(Saml2Error error, String message, Throwable cause) {
81+
super(message, cause);
82+
this.setError(error);
83+
}
84+
85+
/**
86+
* Returns the {@link Saml2Error SAML 2.0 Error}.
87+
*
88+
* @return the {@link Saml2Error}
89+
*/
90+
public Saml2Error getError() {
91+
return this.error;
92+
}
93+
94+
private void setError(Saml2Error error) {
95+
Assert.notNull(error, "error cannot be null");
96+
this.error = error;
97+
}
98+
99+
@Override
100+
public String toString() {
101+
final StringBuffer sb = new StringBuffer("Saml2AuthenticationException{");
102+
sb.append("error=").append(error);
103+
sb.append('}');
104+
return sb.toString();
105+
}
106+
}

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

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

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

19+
import org.springframework.security.saml2.Saml2Exception;
20+
1921
/**
2022
* Component that generates an AuthenticationRequest, <code>samlp:AuthnRequestType</code> as defined by
2123
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf
@@ -33,6 +35,7 @@ public interface Saml2AuthenticationRequestFactory {
3335
* accompanying data
3436
* @return XML data in the format of a String. This data may be signed, encrypted, both signed and encrypted or
3537
* neither signed and encrypted
38+
* @throws Saml2Exception when a SAML library exception occurs
3639
*/
3740
String createAuthenticationRequest(Saml2AuthenticationRequest request);
3841
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2002-2019 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.core.SpringSecurityCoreVersion;
20+
import org.springframework.util.Assert;
21+
22+
import java.io.Serializable;
23+
24+
/**
25+
* A representation of an SAML 2.0 Error.
26+
*
27+
* <p>
28+
* At a minimum, an error response will contain an error code.
29+
* The commonly used error code are defined in this class
30+
* or a new codes can be defined in the future as arbitrary strings.
31+
* </p>
32+
* @since 5.2
33+
*/
34+
public class Saml2Error implements Serializable {
35+
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
36+
37+
private final String errorCode;
38+
private final String description;
39+
40+
/**
41+
* Constructs a {@code Saml2Error} using the provided parameters.
42+
*
43+
* @param errorCode the error code
44+
* @param description the error description
45+
*/
46+
public Saml2Error(String errorCode, String description) {
47+
Assert.hasText(errorCode, "errorCode cannot be empty");
48+
this.errorCode = errorCode;
49+
this.description = description;
50+
}
51+
52+
/**
53+
* Returns the error code.
54+
*
55+
* @return the error code
56+
*/
57+
public final String getErrorCode() {
58+
return this.errorCode;
59+
}
60+
61+
/**
62+
* Returns the error description.
63+
*
64+
* @return the error description
65+
*/
66+
public final String getDescription() {
67+
return this.description;
68+
}
69+
70+
@Override
71+
public String toString() {
72+
return "[" + this.getErrorCode() + "] " +
73+
(this.getDescription() != null ? this.getDescription() : "");
74+
}
75+
}

0 commit comments

Comments
 (0)