Sunday, October 1, 2017

Generate SAML 2 AuthnRequest by using OpenSAML 3

Hi,

I will start series of posts on SAML 2 about how to generate messages, parsing them and validating them.

First of all, to dig into the subject I really recommend to read the saml technical overview. It has everything needed to understand SAML and it is well written.

In this post I will show how to generate an AuthN request using Java and we will validate the request by using an online tool : https://www.samltool.com/online_tools.php.

You can check how AuthN requests are used in SAML protocol in the following diagram :

This diagram shows the use case "SP-Initiated SSO". Refer to the technical overview mentioned above to have all details.

To generate the AuthnRequest we will use OpenSAML 3. As the website is stating there isn't a lot of documentation on this version of OpenSAML so I took some time to adapt existing code to get the snippet bellow. The original code can be found on this blog :  http://www.john-james-andersen.com/blog/programming/sample-saml-2-0-authnrequest-in-java.html (thanks to the author of this blog)

package opensamltutorial;
import java.util.UUID;
import org.joda.time.DateTime;
import org.opensaml.core.config.InitializationException;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.NameIDPolicy;
import org.opensaml.saml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml.saml2.core.impl.AuthnContextClassRefBuilder;
import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml.saml2.core.impl.NameIDPolicyBuilder;
import org.opensaml.saml.saml2.core.impl.RequestedAuthnContextBuilder;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
public class AuthnGeneration {
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException {
try {
InitializationService.initialize();
} catch (InitializationException e) {
e.printStackTrace();
}
AuthnRequest authnRequest = generateAuthnRequest();
Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(authnRequest);
org.w3c.dom.Element authDOM;
try {
authDOM = marshaller.marshall(authnRequest);
System.out.println(SerializeSupport.prettyPrintXML(authDOM));
} catch (MarshallingException e) {
e.printStackTrace();
}
}
private static AuthnRequest generateAuthnRequest()
throws IllegalArgumentException, SecurityException, IllegalAccessException {
XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
AuthnRequestBuilder authnRequestBuilder = (AuthnRequestBuilder) builderFactory.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
AuthnRequest authnRequest = authnRequestBuilder.buildObject();
authnRequest.setForceAuthn(true);
authnRequest.setIsPassive(false);
authnRequest.setIssueInstant(new DateTime());
authnRequest.setDestination("http://idp.example.com/SSOService.php");
authnRequest.setProtocolBinding(SAMLConstants.SAML2_ARTIFACT_BINDING_URI);
authnRequest.setAssertionConsumerServiceURL("http://sp.example.com/demo1/index.php?acs");
authnRequest.setID("ONELOGIN_" + UUID.randomUUID().toString());
Issuer issuer = ((IssuerBuilder) builderFactory.getBuilder(Issuer.DEFAULT_ELEMENT_NAME)).buildObject();
issuer.setValue("http://sp.example.com/demo1/metadata.php");
authnRequest.setIssuer(issuer);
NameIDPolicy nameIDPolicy = ((NameIDPolicyBuilder) builderFactory.getBuilder(NameIDPolicy.DEFAULT_ELEMENT_NAME)).buildObject();
nameIDPolicy.setSPNameQualifier("SERVICE_PROVIDER_ID");
nameIDPolicy.setAllowCreate(true);
nameIDPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient");
authnRequest.setNameIDPolicy(nameIDPolicy);
RequestedAuthnContext requestedAuthnContext = ((RequestedAuthnContextBuilder) builderFactory.getBuilder(RequestedAuthnContext.DEFAULT_ELEMENT_NAME)).buildObject();
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM);
AuthnContextClassRef authnContextClassRef = ((AuthnContextClassRefBuilder) builderFactory.getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME)).buildObject();
authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
authnRequest.setRequestedAuthnContext(requestedAuthnContext);
return authnRequest;
}
}
view raw AuthnGeneration hosted with ❤ by GitHub

To make it run, add the following dependencies in your Maven project

<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml-core</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml-saml-api</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml-saml-impl</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>net.shibboleth.utilities</groupId>
    <artifactId>java-support</artifactId>
    <version>7.3.0</version>
</dependency>

The last dependency (net.shibboleth.utilities) is for the pretty print of the SAML message.

By running the code, here there is the SAML AuthnRequest that is generated :

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
    AssertionConsumerServiceURL="http://sp.example.com/demo1/index.php?acs"
    Destination="http://idp.example.com/SSOService.php"
    ForceAuthn="true" ID="ONELOGIN_6f319749-6db0-4ac6-be72-cb223d5870a4"
    IsPassive="false" IssueInstant="2017-10-01T18:20:31.096Z"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
    Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/demo1/metadata.php</saml2:Issuer>
    <saml2p:NameIDPolicy AllowCreate="true"
        Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="SERVICE_PROVIDER_ID"/>
    <saml2p:RequestedAuthnContext Comparison="minimum">
        <saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>
    </saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>

The last thing to do is to validate this message, we will use this nice online tool : https://www.samltool.com/validate_authn_req.php. Another way to validate the message would by using OpenSAML itself. It may be the subject of another post on this blog.


I hope this article will help you getting started with SAML message generation. To continue on the subject I will try to add digital signature to this generated message in order to improve security but it will be in another post.

Thank you for reading!

3 comments:

  1. Great content.Please move on with generating and validating assertions/responses.

    ReplyDelete
  2. HI
    I am unable to run this code on eclipse.
    error:
    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
    Exception in thread "main" java.lang.NoClassDefFoundError: com/google/common/base/Function
    at net.shibboleth.utilities.java.support.xml.AttributeSupport.getAttributeValueAsQName(AttributeSupport.java:334)
    at org.opensaml.core.xml.config.XMLConfigurator.initializeObjectProviders(XMLConfigurator.java:231)
    at org.opensaml.core.xml.config.XMLConfigurator.load(XMLConfigurator.java:203)
    at org.opensaml.core.xml.config.XMLConfigurator.load(XMLConfigurator.java:188)

    ReplyDelete
  3. Hi,
    I don't know why you get this error. Guava is dependency of net.shibboleth.utilities/java-support and maven should install it.
    Try 'mvn install' from command line.

    ReplyDelete