Quantcast
Channel: Oracle Bloggers
Viewing all articles
Browse latest Browse all 19780

Implementing an IdP Discovery Service

$
0
0

As discussed in my previous article, OIF/SP can be configured to use a remote IdP Discovery Service whose function is to determine which IdP to use for the Federation SSO operation.

The "Identity Provider Discovery Service Protocol and Profile" SAML 2.0 specification published by OASIS defines the interaction protocol between a SAML 2.0 SP and an IdP Discovery Service.

In this article, I will implement a sample IdP Discovery Service, and then I will configure OIF/SP to use that service:

  • The service needs to support the protocol defined by the "Identity Provider Discovery Service Protocol and Profile" SAML 2.0 specification
  • The service will be an HTTP service and can be deployed anywhere
  • OIF/SP will be configured to redirect the user to that remote service when starting a Federation SSO operation.

Enjoy the reading!

IdP Discovery Service Protocol


As mentioned in my previous article, the IdP Discovery Service flow is described in the specification (http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-idp-discovery.pdf?) and is made of the following steps:

  • SP is configured to use a remote IdP Discovery Service to determine the IdP to be used for the Federation SSO operation
  • The SP redirects the user to the IdP Discovery Service via a 302 HTTP redirect and provides the following parameters in the query string
    • entityID: the Issuer/ProviderID of OIF/SP
    • returnIDParam: the name of the query string parameter that the service needs to use for the parameter containing the IdP ProviderID value, when redirecting the user back to OIF/SP
    • return: the URL to use to redirect the user to OIF/SP
  • The service determines the IdP to use
  • The service redirects the user to OIF/SP via a 302 HTTP redirect based on the query parameter "return" specified by the SP and provides the following parameters in the query string
  • A query parameter containing the the IdP ProviderID value; the name of that query parameter is specified by the SP in the returnIDParam query parameter.

Apart from the protocol exchange, the service can be implemented in any way deemed acceptable by the implementer.

Custom Service


Overview

In this example, I will write a service that will:

  • Be aware of a list of known IdPs, referenced by the ProviderID/Issuer identifiers
  • Let the user select the IdP to use from a drop down list
  • Save the user's choice in a cookie called IDPDiscService
  • At runtime, the service will check if the IDPDiscService is present:
    • If present and contains a valid IdP, then the service will automatically redirect the user back to the SP with the IdP's ProviderID/Issuer: no user interaction will take place
    • Otherwise, the service will display a page containing a dropdown list of the known IdPs

In terms of implementation, I will:

  • Use a JSP page
  • Deploy the WAR/JSP on a remote J2EE container, different from the WLS server where OAM/OIF are running

Sample Pages

My custom IdP Discovery Service will be made of two JSP pages:

  • landing.jsp
    • Page where the user is redirected from the SP to the service
    • Will first check if the IDPDiscService cookie is present and contains a known IdP
    • If present, it will redirect the user back to the SP with the IdP's ProviderID/Issuer
    • Otherwise it will display a page asking the user to select an IdP
    • The page asking the user to select an IdP will be made of a drop down
    • Upon submitting the choice, the user will post data to /idpdiscoveryservice/submit.jsp page
    • This page will be accessible via /idpdiscoveryservice/landing.jsp
  • submit.jsp
    • Page where the user posts its choice, from the landing.jsp
    • Will check that the selected IdP is valid
    • Will save the IdP in the IDPDiscService cookie
    • The value will be the Base64 encoded IdP's ProviderID/Issuer
    • The cookie will be marked persistent
    • Will redirect the user back to the SP with the IdP's ProviderID/Issuer

Implementation


Landing Page

<%@ page buffer="5kb" autoFlush="true" session="false"%>
<%@ page language="java" import="java.util.*,sun.misc.*,java.net.*"%>
<%
response.setHeader("Expires", "Sat, 1 Jan 2000 00:00:00 GMT");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");

// list of known IdPs, keys being the displayed names, and values the ProviderID/Issuer IDs
Map idps = new HashMap();
idps.put("AcmeIdP", "https://acme.com/fed");
idps.put("IdP1", "https://idp1.com/saml");
idps.put("MyIdentiyProvider.com", "https://myidentityprovider.com/saml2.0");

// entityID of the requesting SP
String entityID = request.getParameter("entityID");
// the query parameter that will contain the IdP's ProviderID/Issuer when
// redirecting the user back to the SP
String returnIDParam = request.getParameter("returnIDParam");
// the URL where the user should be redirected at the SP
String returnURL = request.getParameter("return");

// check if the IDPDiscService cookie is set, and if it is a known IdP
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0)
{
  for (int i = 0; i < cookies.length; i++)
  {
    if ("IDPDiscService".equals(cookies[i].getName()))
    {
      // decode the idp
      BASE64Decoder b64 = new BASE64Decoder();
      String idp = new String(b64.decodeBuffer(cookies[i].getValue()));
      if (idps.containsValue(idp))
      {
        // redirects to the SP
        StringBuffer redirectStringBuffer = new StringBuffer(returnURL);
        if (returnURL.indexOf('?') == -1)
          redirectStringBuffer.append("?");
        else if (returnURL.charAt(returnURL.length() - 1) != '&')
          redirectStringBuffer.append("&");
        redirectStringBuffer.append(returnIDParam);
        redirectStringBuffer.append("=");
        redirectStringBuffer.append(URLEncoder.encode(idp));
        response.sendRedirect(redirectStringBuffer.toString());
        return;
      }
    }
  }
}
%>

<html>
  <head>
    <title>Select your Single Sign-On Server</title>
  </head>
  <body>
    <p style="text-align: center;">Select your Single Sign-On Server</p>
    <form action="/idpdiscoveryservice/submit.jsp" method="post" name="idpselection">
      <input name="entityID" type="hidden" value="<%=entityID%>" />
      <input name="returnIDParam" type="hidden" value="<%=returnIDParam%>" />
      <input name="return" type="hidden" value="<%=returnURL%>" />
      <p style="text-align: center;">Single Sign-On Server:
        <select name="selectedidp">
<%
Iterator idpsIterator = idps.keySet().iterator();
while(idpsIterator.hasNext())
{
  String displayIdP = (String)idpsIterator.next();
  String providerIDIdP = (String)idps.get(displayIdP);
%>
          <option value="<%=providerIDIdP%>"><%=displayIdP%></option>
<%
}
%>
        </select>
      </p>
      <p style="text-align: center;">
        <input name="submit" type="submit" value="Submit" />
      </p>
    </form>
  </body>
</html>

Submit Page

<%@ page buffer="5kb" autoFlush="true" session="false"%>
<%@ page language="java" import="java.util.*,sun.misc.*,java.net.*"%>
<%
response.setHeader("Expires", "Sat, 1 Jan 2000 00:00:00 GMT");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");

// list of known IdPs with values being the ProviderID/Issuer IDs
List idps = new ArrayList();
idps.add("https://acme.com/fed");
idps.add("https://idp1.com/saml");
idps.add("https://myidentityprovider.com/saml2.0");

// entityID of the requesting SP
String entityID = request.getParameter("entityID");
// the query parameter that will contain the IdP's ProviderID/Issuer when
// redirecting the user back to the SP
String returnIDParam = request.getParameter("returnIDParam");
// the URL where the user should be redirected at the SP
String returnURL = request.getParameter("return");
// the idp selected by the user
String idp = request.getParameter("selectedidp");

// check that the selected IdP is one of the known IdPs
if (!idps.contains(idp))
  throw new Exception("Unknown IdP");

// save the idp in the IDPDiscService cookie
BASE64Encoder b64Encoder = new BASE64Encoder();
response.addHeader("Set-Cookie", "IDPDiscService=" +
    b64Encoder.encode(idp.getBytes()) +
    "; expires=Wed, 01-Jan-2020 00:00:00 GMT; path=/");

// redirects to the SP
StringBuffer redirectStringBuffer = new StringBuffer(returnURL);
if (returnURL.indexOf('?') == -1)
  redirectStringBuffer.append("?");
else if (returnURL.charAt(returnURL.length() - 1) != '&')
  redirectStringBuffer.append("&");
redirectStringBuffer.append(returnIDParam);
redirectStringBuffer.append("=");
redirectStringBuffer.append(URLEncoder.encode(idp));
response.sendRedirect(redirectStringBuffer.toString());
%>

Packaging

Having put the landing.jsp and submit.jsp into a directory only containing those files, I created the idpDiscService.war  WAR file using the JAR tool:

jar cvf idpDiscService.war *.jsp

The content of that WAR file can be seen via the unzip command:

unzip -l idpDiscService.war
Archive:  idpDiscService.war
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  01-09-2014 19:00   META-INF/
       76  01-09-2014 19:00   META-INF/MANIFEST.MF
     2898  01-09-2014 19:57   landing.jsp
     1838  01-09-2014 19:57   submit.jsp
---------                     -------
     4812                     4 files

I then deployed this WAR file on a J2EE container, using /idpdiscoveryservice as the root path. That way both pages are accessible using /idpdiscoveryservice/landing.jsp and /idpdiscoveryservice/submit.jsp.

Configuring OIF/SP

To configure OIF/SP to use an IdP Discovery Service, perform the following steps:

  • Enter the WLST environment by executing:
    $IAM_ORACLE_HOME/common/bin/wlst.sh
  • Connect to the WLS Admin server:
    connect()
  • Navigate to the Domain Runtime branch:
    domainRuntime()
  • Enable/disable OIF/SP to use an IdP Discovery Service:
    putBooleanProperty("/spglobal/idpdiscoveryserviceenabled", "true")
  • Set the location of the remote IdP Discovery Service:
    putStringProperty("/spglobal/idpdiscoveryserviceurl", "http://remote.idp.disc.service.com/idpdiscoveryservice/landing.jsp")
  • Exit the WLST environment:
    exit()

Test


When OIF/SP is invoked to start a Federation SSO, it will redirect the user to my custom IdP Discovery Service (/idpdiscoveryservice/landing.jsp)

On the first visit, the user will be presented with a drop down list and prompted to select an IdP SSO Server:

Upon submitting the choice to /idpdiscoveryservice/submit.jsp, the page will validate the choice, save it into the IDPDiscService cookie and redirect the user to OIF/SP with the IdP's ProviderID: from there, OIF/SP will start Federation SSO with that IdP.

The next times OIF/SP starts a Federation SSO for that user:

  • The server will redirect the user to my custom IdP Discovery Service (/idpdiscoveryservice/landing.jsp)
  • The page will detect the IDPDiscService cookie and decode the IdP's ProviderID
  • The page will redirect the user to OIF/SP with the IdP's ProviderID
  • OIF/SP will start Federation SSO with that IdP.


In the next article, I will show how to create a custom Authentication Module in OIF/SP that will be made of the existing OIF Federation Authentication Plugins and a custom plugin.
Cheers,
Damien Carru


Viewing all articles
Browse latest Browse all 19780

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>