In this blog post i will explain how to write a sample SSO service provider code using wso2is as the identity provider and openSAML as the supporting library to create SAML request.
First download the wso2IS from [1] and openSAML release from [2]. Then startup the wso2is. Create a new java web application project and import below jars from opensaml lib and lib/endorsed folders.
serializer-2.10.0
xalan-2.7.1
xercesImpl-2.10.0
xml-apis-2.10.0
xmltooling-1.4.1
xmlsec-1.5.6
slf4j-api-1.7.5
openws-1.5.1
opensaml-2.6.1
joda-time-2.2
esapi-2.0.1
commons-lang-2.6
Also include “axiom-api.jar” from apache axis project which has the UIDGenerator.java class.
Lets take our service provider name is “test.com” & our consumer landing page is “http://localhost:8080/test.com/home.jsp”. (You have to register a new service provider with above fields in wso2 identity server. To do so please refer [3] )
You can use below code to sent login & logout saml requests.
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLVersion;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.AttributeStatement;
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.LogoutRequest;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.NameIDPolicy;
import org.opensaml.saml2.core.RequestAbstractType;
import org.opensaml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.SessionIndex;
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.LogoutRequestBuilder;
import org.opensaml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml2.core.impl.NameIDPolicyBuilder;
import org.opensaml.saml2.core.impl.RequestedAuthnContextBuilder;
import org.opensaml.saml2.core.impl.SessionIndexBuilder;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallerFactory;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.XMLHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
public class SAMLConsumer {
private static final Logger logger = LoggerFactory.getLogger(SAMLConsumer.class);
private static boolean isBootstraped = false;
private static final String ISSUER_URL = “test.com”;
private static final String CONSUMER_URL = “http://localhost:8080/test.com/home.jsp”;
private static final String NAME_SPACE_URI_ISSUER = “urn:oasis:names:tc:SAML:2.0:assertion”;
private static final String NAME_SPACE_URI_AUTH = “urn:oasis:names:tc:SAML:2.0:protocol”;
private static final String SAML_TRANSPORT_TYPE_PWD_PROTECTED = “urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport”;
private static final String SAML_TRANSPORT_PROTOCOL_BINDING = “urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST”;
private static final String LOCAL_NAME_ISSUER = “Issuer”;
private static final String LOCAL_NAME_AUTHRQ = “AuthnRequest”;
private static final String NAME_SPACE_PREFIX = “samlp”;
private static final String AUTH_CNTXT_REF = “AuthnContextClassRef”;
private static final String SAML = “saml”;
public static String buildLoginRequest() throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException, MarshallingException, IOException {
logger.info(“started”);
IssuerBuilder issuerBuilder = new IssuerBuilder();
Issuer issuer = issuerBuilder.buildObject(NAME_SPACE_URI_ISSUER, LOCAL_NAME_ISSUER, “samlp”);
issuer.setValue(ISSUER_URL);
NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
nameIdPolicy.setFormat(“urn:oasis:names:tc:SAML:2.0:nameid-format:persistent”);
nameIdPolicy.setSPNameQualifier(“Isser”);
nameIdPolicy.setAllowCreate(new Boolean(true));
AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
AuthnContextClassRef authnContextClassRef =
authnContextClassRefBuilder.buildObject(NAME_SPACE_URI_ISSUER,
AUTH_CNTXT_REF, SAML);
authnContextClassRef.setAuthnContextClassRef(SAML_TRANSPORT_TYPE_PWD_PROTECTED);
RequestedAuthnContextBuilder requestedAuthnContextBuilder =
new RequestedAuthnContextBuilder();
RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
DateTime issueInstant = new DateTime();
AuthnRequestBuilder authnRequestBuilder = new AuthnRequestBuilder();
AuthnRequest authnRequest = authnRequestBuilder.buildObject(NAME_SPACE_URI_AUTH, LOCAL_NAME_AUTHRQ, NAME_SPACE_PREFIX);
authnRequest.setForceAuthn(new Boolean(false));
authnRequest.setIsPassive(new Boolean(false));
authnRequest.setIssueInstant(issueInstant);
authnRequest.setProtocolBinding(SAML_TRANSPORT_PROTOCOL_BINDING);
authnRequest.setAssertionConsumerServiceURL(CONSUMER_URL);
authnRequest.setIssuer(issuer);
authnRequest.setNameIDPolicy(nameIdPolicy);
authnRequest.setRequestedAuthnContext(requestedAuthnContext);
String authReqRandomId = Integer.toHexString(new Double(Math.random()).intValue());
authnRequest.setID(authReqRandomId);
authnRequest.setVersion(SAMLVersion.VERSION_20);
return marshall(authnRequest);
}
public static String buildLogoutRequest(String user) throws MarshallingException, IOException {
LogoutRequest logoutReq = new LogoutRequestBuilder().buildObject();
logoutReq.setID(createID());
DateTime issueInstant = new DateTime();
logoutReq.setIssueInstant(issueInstant);
logoutReq.setNotOnOrAfter(new DateTime(issueInstant.getMillis() + 5 * 60 * 1000));
IssuerBuilder issuerBuilder = new IssuerBuilder();
Issuer issuer = issuerBuilder.buildObject();
issuer.setValue(ISSUER_URL);
logoutReq.setIssuer(issuer);
NameID nameId = new NameIDBuilder().buildObject();
nameId.setFormat(“urn:oasis:names:tc:SAML:2.0:nameid-format:entity”);
nameId.setValue(user);
logoutReq.setNameID(nameId);
SessionIndex sessionIndex = new SessionIndexBuilder().buildObject();
sessionIndex.setSessionIndex(UIDGenerator.generateUID());
logoutReq.getSessionIndexes().add(sessionIndex);
logoutReq.setReason(“Single Logout”);
return marshall(logoutReq);
}
private static String marshall(RequestAbstractType authnRequest) throws MarshallingException, IOException {
doBootstrap();
Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(authnRequest);
Element authDOM = marshaller.marshall(authnRequest);
StringWriter rspWrt = new StringWriter();
XMLHelper.writeNode(authDOM, rspWrt);
String requestMessage = rspWrt.toString();
System.out.println(requestMessage);
Deflater deflater = new Deflater(Deflater.DEFLATED, true);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);
deflaterOutputStream.write(requestMessage.getBytes());
deflaterOutputStream.close();
/* Encoding the compressed message */
String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream.toByteArray(), Base64.DONT_BREAK_LINES);
String encodedAuthnRequest = URLEncoder.encode(encodedRequestMessage,”UTF-8”).trim();;
return encodedAuthnRequest;
}
public static <T> T createSAMLObject(final Class<T> clazz) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException {
XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();
QName defaultElementName = (QName)clazz.getDeclaredField(“DEFAULT_ELEMENT_NAME”).get(null);
Map<QName, XMLObjectBuilder> builderMap= builderFactory.getBuilders();
System.out.println(“is nul “ + builderMap.get(defaultElementName));
return null;
}
private static void doBootstrap() {
if(!isBootstraped) {
try {
DefaultBootstrap.bootstrap();
isBootstraped = true;
} catch (ConfigurationException e) {
logger.error(“Error calling bootstrap”, e);
}
}
}
public static Map<String, String> processResponse(String response) {
XMLObject resp = null;
try {
resp = unmarshall(response);
} catch (ConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnmarshallingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return getResult(resp);
}
private static XMLObject unmarshall(String responseMessage) throws ConfigurationException,
ParserConfigurationException, SAXException, IOException, UnmarshallingException {
doBootstrap();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
byte[] base64DecodedResponse = Base64.decode(responseMessage.trim());
System.out.println(new String(base64DecodedResponse));
ByteArrayInputStream is = new ByteArrayInputStream(base64DecodedResponse);
Document document = docBuilder.parse(is);
Element element = document.getDocumentElement();
UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
return unmarshaller.unmarshall(element);
}
private static Map<String, String> getResult(XMLObject responseXmlObj) {
if (responseXmlObj.getDOM().getNodeName().equals(“saml2p:LogoutResponse”)) {
logger.error(“user logout”);
return null;
}
Response response = (Response) responseXmlObj;
logger.info(“SAML resp” + response);
Assertion assertion = response.getAssertions().get(0);
Map<String, String> resutls = new HashMap<String, String>();
/*
* If the request has failed, the IDP shouldn’t send an assertion.
* SSO profile spec 4.1.4.2 <Response> Usage
*/
if (assertion != null) {
String subject = assertion.getSubject().getNameID().getValue();
resutls.put(“Subject”, subject); // get the subject
List<AttributeStatement> attributeStatementList = assertion.getAttributeStatements();
if (attributeStatementList != null) {
// we have received attributes of user
Iterator<AttributeStatement> attribStatIter = attributeStatementList.iterator();
while (attribStatIter.hasNext()) {
AttributeStatement statment = attribStatIter.next();
List<Attribute> attributesList = statment.getAttributes();
Iterator<Attribute> attributesIter = attributesList.iterator();
while (attributesIter.hasNext()) {
Attribute attrib = attributesIter.next();
Element value = attrib.getAttributeValues().get(0).getDOM();
String attribValue = value.getTextContent();
resutls.put(attrib.getName(), attribValue);
}
}
}
}
return resutls;
}
public static String createID() {
byte[] bytes = new byte[20]; // 160 bit
new Random().nextBytes(bytes);
char[] charMapping = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’, ‘n’, ‘o’, ‘p’};
char[] chars = new char[40];
for (int i = 0; i < bytes.length; i++) {
int left = (bytes[i] » 4) & 0x0f;
int right = bytes[i] & 0x0f;
chars[i * 2] = charMapping[left];
chars[i * 2 + 1] = charMapping[right];
}
return String.valueOf(chars);
}
}
[1] http://wso2.com/products/identity-server/
[2] https://wiki.shibboleth.net/confluence/display/OpenSAML/Home
[3] http://pavithramadurangi.blogspot.com/2013/09/saml-20-sso-with-wso2-is-450.html