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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
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!
Great content.Please move on with generating and validating assertions/responses.
ReplyDeleteHI
ReplyDeleteI 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)
Hi,
ReplyDeleteI 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.