Create SAML2 SP-Initiated Authnrequest Using Java: High Level Implementation
Create SAML2 SP-Initiated Authnrequest Using Java: High Level Implementation
Initiated authnrequest
using java
Posted on May 6, 2017
Tags: iam, saml2
As of 2017, SAML is still most common standard used in enterprises for Single Sign On
(SSO). SAML2 supports different flows such as IDP initiated flows and SP initiated flows for
addressing different use cases for SSO federation.
I needed to implement SP initiated flow for one of internal use cases. Actually, we were deep
linking from one app to another app and wanted to enforce authentication in between and also
wanted to preserve deep link.
So how can we preserve deep links? does SAML standard support it? Answer is yes, below
image from Okta is good overview of high level flow on how can we preserve deep links.
In this example, I would just show sample Java implementation to generate SAML request
using OpenSAML library.
Here is sample authN request example using HTTP get. We can also submit authN request
using HTTP POST but we will not cover that in this example.
https://{IDP_APP_URL}?
SAMLRequest={SAMLREQUEST_VALUE}&RelayState={RELAY_STATE_VALUE}
ex.
https://dev-
552077.oktapreview.com/app/demodev552077_saml2app_1/exka9jbh72o7D4REL0h7/ss
o/saml?
SAMLRequest=nZPdjtowEIVfJfJ9fkgCYS3CikJXRaJtBNle9M4xk8XdxE49DrBvv07ItlRq0ap
XkezjOTPfmczuz3XlHEGjUDIlIy8gDkiu9kI
%2BpeQxf3Cn5H4%2BQ1ZXDV205iC38LMFNI59J5H2FylptaSKoUAqWQ1IDae7xecNDb2ANloZxV
VFnAUiaGONlkpiW4PegT4KDo%2FbTUoOxjRIfb9ST0J6XNUNky%2Fd10dU
%2BmLqi33jq2fDiPOgNIe
%2BoZSUrEIgznqVkigCXjA2dqPyjrnxJJi6RVmUbhJHfBIW0zgZR1aJGUMUR
%2Fj9FrGFtUTDpElJGIwSNxi7wSQPRzQa01HgRXH0nTjZMMwHIS%2BIbk1eXERIP
%2BV55mZfdzlxvr2htgJyARvS3l1fIQ1vF2ZvIMm8w2apnU4nrwPTA4PzM7v7URySUCWrePtxEx
ySmX9tNRg39IutvV5lqhL85dr%2F
%2FZlWlTotNTBjYRrdQh9NzcztAt2J2LtlL6VNBwUNSEP8X60Niwb7PmW7MgbO5r96XHbLpAV22
OHMuBnA0%2BvKy8pS3UJ55fDuEG7KOOVdaXvcLd1J6X23RMDtZLlmEhulzSWdv%2FYzH5L7B5Dh
%2Bs%2Bfc%2F4K&RelayState=https%3A%2F%2Ffanyv88.com%3A443%2Fhttp%2Fapp1.company.com%3FarticleId%3D1234
Other thing to learn is that once we generate the SAMLRequest in XML form, It’s needed to
be compressed, base64 encoded, url encoded so that it can be correctly decoded by the IDP.
back to top
AuthNRequestBuilder.java
package com.slabs.login.service.login;
import org.opensaml.common.SAMLVersion;
import org.opensaml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.NameIDPolicy;
import org.opensaml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
import org.opensaml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml2.core.impl.NameIDPolicyBuilder;
import org.opensaml.saml2.core.impl.RequestedAuthnContextBuilder;
import java.util.UUID;
import org.joda.time.DateTime;
/**
* Generate an authentication request.
*
* @return AuthnRequest Object
*/
public AuthnRequest buildAuthenticationRequest(String
assertionConsumerServiceUrl, String issuerId) {
//Generate ID
DateTime issueInstant = new DateTime();
AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
AuthnRequest authRequest =
authRequestBuilder.buildObject(SAML2_PROTOCOL, "AuthnRequest", "samlp");
authRequest.setForceAuthn(Boolean.FALSE);
authRequest.setIsPassive(Boolean.FALSE);
authRequest.setIssueInstant(issueInstant);
authRequest.setProtocolBinding(SAML2_POST_BINDING);
authRequest.setAssertionConsumerServiceURL(assertionConsumerServiceUrl);
authRequest.setIssuer(buildIssuer( issuerId));
authRequest.setNameIDPolicy(buildNameIDPolicy());
authRequest.setRequestedAuthnContext(buildRequestedAuthnContext());
authRequest.setID(UUID.randomUUID().toString());
authRequest.setVersion(SAMLVersion.VERSION_20);
return authRequest;
}
/**
* Build the issuer object
*
* @return Issuer object
*/
private static Issuer buildIssuer(String issuerId) {
IssuerBuilder issuerBuilder = new IssuerBuilder();
Issuer issuer = issuerBuilder.buildObject();
issuer.setValue(issuerId);
return issuer;
}
/**
* Build the NameIDPolicy object
*
* @return NameIDPolicy object
*/
private static NameIDPolicy buildNameIDPolicy() {
NameIDPolicy nameIDPolicy = new NameIDPolicyBuilder().buildObject();
nameIDPolicy.setFormat(SAML2_NAME_ID_POLICY);
nameIDPolicy.setAllowCreate(Boolean.TRUE);
return nameIDPolicy;
}
/**
* Build the RequestedAuthnContext object
*
* @return RequestedAuthnContext object
*/
private static RequestedAuthnContext buildRequestedAuthnContext() {
//Create AuthnContextClassRef
AuthnContextClassRefBuilder authnContextClassRefBuilder = new
AuthnContextClassRefBuilder();
AuthnContextClassRef authnContextClassRef =
authnContextClassRefBuilder.buildObject(SAML2_ASSERTION,
"AuthnContextClassRef", "saml");
authnContextClassRef.setAuthnContextClassRef(SAML2_PASSWORD_PROTECTED_TRANS
PORT);
//Create RequestedAuthnContext
RequestedAuthnContextBuilder requestedAuthnContextBuilder = new
RequestedAuthnContextBuilder();
RequestedAuthnContext requestedAuthnContext =
requestedAuthnContextBuilder.buildObject();
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.E
XACT);
requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
return requestedAuthnContext;
}
back to top
LoginService.java
package com.slabs.login.service.login;
LoginServiceImpl.java
package com.slabs.login.service.login;
import org.opensaml.DefaultBootstrap;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.XMLHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.StringWriter;
import java.net.URLEncoder;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
@Service
public class LoginServiceImpl implements LoginService {
{
/* Initializing the OpenSAML library, Should be in some central place
*/
try {
DefaultBootstrap.bootstrap();
}
catch(Exception ex){
LOGGER.error("Unable to initialize SAML", ex);
throw new RuntimeException("Unable to initialize SAML");
}
}
/*
* Return's redirectUrl
* - Creates SAML2 AuthN object
* - Compresses it
* - Base 64 encode it
* - URL encode it
* - Appends RelayState
*/
@Override
public String getAuthNRedirectUrl(String idpAppURL, String relayState,
String assertionConsumerServiceUrl, String issuerId){
try {
/*
* Converts AuthN object to xml, compresses it, base64 encode it and url
encode it
*/
private String generateSAMLRequest(AuthnRequest authRequest) throws
Exception {
Marshaller marshaller =
org.opensaml.Configuration.getMarshallerFactory().getMarshaller(authRequest
);
org.w3c.dom.Element authDOM = marshaller.marshall(authRequest);
StringWriter rspWrt = new StringWriter();
XMLHelper.writeNode(authDOM, rspWrt);
String messageXML = rspWrt.toString();
}
}