WSO2 IS SAML SSO using openSAML java library
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












