0% found this document useful (0 votes)
36 views9 pages

Create SAML2 SP-Initiated Authnrequest Using Java: High Level Implementation

The document describes how to generate a SAML 2.0 authentication request (AuthNRequest) from a Java application to implement a service provider (SP) initiated single sign-on flow. It provides code samples to build the AuthNRequest object using the OpenSAML library, including setting parameters like the issuer, name ID policy, requested authentication context, and preserving deep links via the RelayState parameter. The SAML request is then converted to XML, compressed, base64 encoded, and URL encoded to generate the final redirect URL to send the user's browser to the identity provider (IDP) for authentication.

Uploaded by

Lav Kumar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
36 views9 pages

Create SAML2 SP-Initiated Authnrequest Using Java: High Level Implementation

The document describes how to generate a SAML 2.0 authentication request (AuthNRequest) from a Java application to implement a service provider (SP) initiated single sign-on flow. It provides code samples to build the AuthNRequest object using the OpenSAML library, including setting parameters like the issuer, name ID policy, requested authentication context, and preserving deep links via the RelayState parameter. The SAML request is then converted to XML, compressed, base64 encoded, and URL encoded to generate the final redirect URL to send the user's browser to the identity provider (IDP) for authentication.

Uploaded by

Lav Kumar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 9

Create SAML2 SP-

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.

High level Implementation


As noted in above diagram, Relaystate can be used to preserve deep links. We will set the
RelayState of the SAML request with the deep link. When SAML response comes back, SP
can use the RelayState to redirect the user to the appropriate resource.

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.

Below is the sample code. Full code is present @ https://github.com/sunieldalal/loginapp or


can be cloned using below command.

git clone https://github.com/sunieldalal/loginapp

Add AuthNRequest Builder

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;

public class AuthNRequestBuilder {

private static final String SAML2_NAME_ID_POLICY =


"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent";
private static final String SAML2_PROTOCOL =
"urn:oasis:names:tc:SAML:2.0:protocol";
private static final String SAML2_POST_BINDING =
"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
private static final String SAML2_PASSWORD_PROTECTED_TRANSPORT =
"urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport";
private static final String SAML2_ASSERTION =
"urn:oasis:names:tc:SAML:2.0:assertion";

/**
* 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;
}

Add Logic to Generate SAML2 compliant


AuthN SAML request

back to top
LoginService.java
package com.slabs.login.service.login;

public interface LoginService {

public String getAuthNRedirectUrl(String idpAppURL, String relayState,


String assertionConsumerServiceUrl,
String issuerId);
}

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 {

private static Logger LOGGER =


LoggerFactory.getLogger(LoginServiceImpl.class.getName());

{
/* 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){

LOGGER.info("idpAppURL=" + idpAppURL + " relayState=" + relayState + "


assertionConsumerServiceUrl=" + assertionConsumerServiceUrl + " issuerId="
+ issuerId);
String url = null;

try {

AuthNRequestBuilder authNRequestBuilder = new AuthNRequestBuilder();


AuthnRequest authRequest =
authNRequestBuilder.buildAuthenticationRequest(assertionConsumerServiceUrl,
issuerId);
String samlRequest = generateSAMLRequest(authRequest);

// Prepare final Url


url = idpAppURL + "?SAMLRequest=" + samlRequest + "&RelayState=" +
URLEncoder.encode(relayState,"UTF-8");

} catch (Exception ex) {


LOGGER.error("Exception while creating AuthN request - " +
ex.getMessage(), ex);
throw new RuntimeException("Unable to generate redirect Url");
}

LOGGER.debug("redirect url is = " + url);


return url;

/*
* 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();

Deflater deflater = new Deflater(Deflater.DEFLATED, true);


ByteArrayOutputStream byteArrayOutputStream = new
ByteArrayOutputStream();
DeflaterOutputStream deflaterOutputStream = new
DeflaterOutputStream(byteArrayOutputStream, deflater);
deflaterOutputStream.write(messageXML.getBytes());
deflaterOutputStream.close();
String samlRequest =
Base64.encodeBytes(byteArrayOutputStream.toByteArray(),
Base64.DONT_BREAK_LINES);
return URLEncoder.encode(samlRequest,"UTF-8");

}
}

Hope it helps to save some time!


References

 Following Okta link provide good overview of SP initiated login


<http://developer.okta .com/standards/SAML/#understanding-sp-
initiated-login-flow>
 This example is inspired from <http://www.john-james-andersen
.com/blog/programming/sample-saml-2-0-authnrequest-in-java.html>.

You might also like