Skip to content

propagate saml authentication exception #7375 #7477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@
import org.springframework.util.Assert;

import org.joda.time.DateTime;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.security.SecurityException;
import org.opensaml.xmlsec.signature.support.SignatureException;

import java.time.Clock;
import java.time.Instant;
Expand Down Expand Up @@ -51,16 +48,11 @@ public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
issuer.setValue(request.getLocalSpEntityId());
auth.setIssuer(issuer);
auth.setDestination(request.getWebSsoUri());
try {
return this.saml.toXml(
auth,
request.getCredentials(),
request.getLocalSpEntityId()
);
}
catch (MarshallingException | SignatureException | SecurityException e) {
throw new IllegalStateException(e);
}
return this.saml.toXml(
auth,
request.getCredentials(),
request.getLocalSpEntityId()
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@
*/
package org.springframework.security.saml2.provider.service.authentication;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;

import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.credentials.Saml2X509Credential;

Expand Down Expand Up @@ -59,6 +51,14 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;

import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.Arrays.asList;
Expand Down Expand Up @@ -165,10 +165,7 @@ <T> T buildSAMLObject(final Class<T> clazz) {
QName defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null);
return (T) getBuilderFactory().getBuilder(defaultElementName).buildObject(defaultElementName);
}
catch (IllegalAccessException e) {
throw new Saml2Exception("Could not create SAML object", e);
}
catch (NoSuchFieldException e) {
catch (NoSuchFieldException | IllegalAccessException e) {
throw new Saml2Exception("Could not create SAML object", e);
}
}
Expand All @@ -177,6 +174,28 @@ XMLObject resolve(String xml) {
return resolve(xml.getBytes(StandardCharsets.UTF_8));
}

String toXml(XMLObject object, List<Saml2X509Credential> signingCredentials, String localSpEntityId) {
if (object instanceof SignableSAMLObject && null != hasSigningCredential(signingCredentials)) {
signXmlObject(
(SignableSAMLObject) object,
signingCredentials,
localSpEntityId
);
}
final MarshallerFactory marshallerFactory = XMLObjectProviderRegistrySupport.getMarshallerFactory();
try {
Element element = marshallerFactory.getMarshaller(object).marshall(object);
return SerializeSupport.nodeToString(element);
} catch (MarshallingException e) {
throw new Saml2Exception(e);
}
}

/*
* ==============================================================
* PRIVATE METHODS
* ==============================================================
*/
private XMLObject resolve(byte[] xml) {
XMLObject parsed = parse(xml);
if (parsed != null) {
Expand All @@ -200,18 +219,6 @@ private UnmarshallerFactory getUnmarshallerFactory() {
return XMLObjectProviderRegistrySupport.getUnmarshallerFactory();
}

String toXml(XMLObject object, List<Saml2X509Credential> signingCredentials, String localSpEntityId)
throws MarshallingException, SignatureException, SecurityException {
if (object instanceof SignableSAMLObject && null != hasSigningCredential(signingCredentials)) {
signXmlObject(
(SignableSAMLObject) object,
getSigningCredential(signingCredentials, localSpEntityId)
);
}
final MarshallerFactory marshallerFactory = XMLObjectProviderRegistrySupport.getMarshallerFactory();
Element element = marshallerFactory.getMarshaller(object).marshall(object);
return SerializeSupport.nodeToString(element);
}

private Saml2X509Credential hasSigningCredential(List<Saml2X509Credential> credentials) {
for (Saml2X509Credential c : credentials) {
Expand All @@ -222,29 +229,34 @@ private Saml2X509Credential hasSigningCredential(List<Saml2X509Credential> crede
return null;
}

private void signXmlObject(SignableSAMLObject object, Credential credential)
throws MarshallingException, SecurityException, SignatureException {
SignatureSigningParameters parameters = new SignatureSigningParameters();
parameters.setSigningCredential(credential);
parameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
parameters.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
SignatureSupport.signObject(object, parameters);
}

private Credential getSigningCredential(List<Saml2X509Credential> signingCredential,
String localSpEntityId
) {
Saml2X509Credential credential = hasSigningCredential(signingCredential);
if (credential == null) {
throw new IllegalArgumentException("no signing credential configured");
throw new Saml2Exception("no signing credential configured");
}
BasicCredential cred = getBasicCredential(credential);
cred.setEntityId(localSpEntityId);
cred.setUsageType(UsageType.SIGNING);
return cred;
}

private void signXmlObject(SignableSAMLObject object, List<Saml2X509Credential> signingCredentials, String entityId) {
SignatureSigningParameters parameters = new SignatureSigningParameters();
Credential credential = getSigningCredential(signingCredentials, entityId);
parameters.setSigningCredential(credential);
parameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
parameters.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
try {
SignatureSupport.signObject(object, parameters);
} catch (MarshallingException | SignatureException | SecurityException e) {
throw new Saml2Exception(e);
}

}

private BasicX509Credential getBasicCredential(Saml2X509Credential credential) {
return CredentialSupport.getSimpleCredential(
credential.getCertificate(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.util.Assert;

/**
* This exception is thrown for all SAML 2.0 related {@link Authentication} errors.
*
* <p>
* There are a number of scenarios where an error may occur, for example:
* <ul>
* <li>The response or assertion request is missing or malformed</li>
* <li>Missing or invalid subject</li>
* <li>Missing or invalid signatures</li>
* <li>The time period validation for the assertion fails</li>
* <li>One of the assertion conditions was not met</li>
* <li>Decryption failed</li>
* <li>Unable to locate a subject identifier, commonly known as username</li>
* </ul>
*
* @since 5.2
*/
public class Saml2AuthenticationException extends AuthenticationException {
private Saml2Error error;

/**
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
*
* @param error the {@link Saml2Error SAML 2.0 Error}
*/
public Saml2AuthenticationException(Saml2Error error) {
this(error, error.getDescription());
}

/**
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
*
* @param error the {@link Saml2Error SAML 2.0 Error}
* @param cause the root cause
*/
public Saml2AuthenticationException(Saml2Error error, Throwable cause) {
this(error, cause.getMessage(), cause);
}

/**
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
*
* @param error the {@link Saml2Error SAML 2.0 Error}
* @param message the detail message
*/
public Saml2AuthenticationException(Saml2Error error, String message) {
super(message);
this.setError(error);
}

/**
* Constructs a {@code Saml2AuthenticationException} using the provided parameters.
*
* @param error the {@link Saml2Error SAML 2.0 Error}
* @param message the detail message
* @param cause the root cause
*/
public Saml2AuthenticationException(Saml2Error error, String message, Throwable cause) {
super(message, cause);
this.setError(error);
}

/**
* Returns the {@link Saml2Error SAML 2.0 Error}.
*
* @return the {@link Saml2Error}
*/
public Saml2Error getError() {
return this.error;
}

private void setError(Saml2Error error) {
Assert.notNull(error, "error cannot be null");
this.error = error;
}

@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Saml2AuthenticationException{");
sb.append("error=").append(error);
sb.append('}');
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

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

import org.springframework.security.saml2.Saml2Exception;

/**
* Component that generates an AuthenticationRequest, <code>samlp:AuthnRequestType</code> as defined by
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf
Expand All @@ -33,6 +35,7 @@ public interface Saml2AuthenticationRequestFactory {
* accompanying data
* @return XML data in the format of a String. This data may be signed, encrypted, both signed and encrypted or
* neither signed and encrypted
* @throws Saml2Exception when a SAML library exception occurs
*/
String createAuthenticationRequest(Saml2AuthenticationRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.util.Assert;

import java.io.Serializable;

/**
* A representation of an SAML 2.0 Error.
*
* <p>
* At a minimum, an error response will contain an error code.
* The commonly used error code are defined in this class
* or a new codes can be defined in the future as arbitrary strings.
* </p>
* @since 5.2
*/
public class Saml2Error implements Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

private final String errorCode;
private final String description;

/**
* Constructs a {@code Saml2Error} using the provided parameters.
*
* @param errorCode the error code
* @param description the error description
*/
public Saml2Error(String errorCode, String description) {
Assert.hasText(errorCode, "errorCode cannot be empty");
this.errorCode = errorCode;
this.description = description;
}

/**
* Returns the error code.
*
* @return the error code
*/
public final String getErrorCode() {
return this.errorCode;
}

/**
* Returns the error description.
*
* @return the error description
*/
public final String getDescription() {
return this.description;
}

@Override
public String toString() {
return "[" + this.getErrorCode() + "] " +
(this.getDescription() != null ? this.getDescription() : "");
}
}
Loading