Implementing an IdP Discovery Service
As discussed earlier, OAM/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.
This article implements a sample IdP Discovery Service, and then configures OAM/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 is an HTTP service and can be deployed anywhere
-
OAM/SP is configured to redirect the user to that remote service when starting a Federation SSO operation
IdP Discovery Service Protocol
As mentioned earlier, 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 OAM/SP -
returnIDParam
: the name of the query string parameter that the service needs to use for the parameter containing the IdPProviderID
value, when redirecting the user back to OAM/SP -
return
: the URL to use to redirect the user to OAM/SP
-
-
The service determines the IdP to use
-
The service redirects the user to OAM/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 IdP
ProviderID
value; the name of that query parameter is specified by the SP in thereturnIDParam
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, 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 checks if the
IDPDiscService
is present -
If present and contains a valid IdP, then the service automatically redirects the user back to the SP with the IdP’s
ProviderID
/Issuer
: no user interaction takes place -
Otherwise, the service displays a page containing a dropdown list of the known IdPs
In terms of implementation:
-
Use a JSP page
-
Deploy the WAR/JSP on a remote J2EE container, different from the WLS server where OAM is running
Sample Pages
The custom IdP Discovery Service is made of two JSP pages:
-
landing.jsp
-
Page where the user is redirected from the SP to the service
-
First check if the
IDPDiscService
cookie is present and contains a known IdP -
If present, it redirects the user back to the SP with the IdP’s
ProviderID
/Issuer
-
Otherwise it displays a page asking the user to select an IdP
-
The page asking the user to select an IdP is made of a drop down
-
Upon submitting the choice, the user posts data to
/idpdiscoveryservice/submit.jsp
page -
This page is accessible via
/idpdiscoveryservice/landing.jsp
-
submit.jsp
Page where the user posts its choice, from thelanding.jsp
-
Saves the IdP in the
IDPDiscService
cookie -
The value is
Base64
encoded IdP’s -
ProviderID
/Issuer
-
The cookie is marked persistent
-
redirects the user back to the SP with the
-
IdP’s
ProviderID
/Issuer
Implementation
Landing Page
<%@ page buaer="5kb" autoFlush="true" session="false"%\> \<%@ page language="java"import="java.util.\*,sun.misc.\*,java.net.\*"%>
<%response.setHeader("Expires", "Sat, 1 Jan 200000: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 contains 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.decodeBuaer(cookies\[i\].getValue()));
if (idps.containsValue(idp))
{
// redirects to the SP
StringBuaer redirectStringBuaer = new
StringBuaer(returnURL);
if (returnURL.indexOf('?') == -1)
redirectStringBuaer.append("?");
else if (returnURL.charAt(returnURL.length() - 1)
!= '&')
redirectStringBuaer.append("&");
redirectStringBuaer.append(returnIDParam);
redirectStringBuaer.append("="); redirectStringBuaer.append(URLEncoder.encode(idp));
response.sendRedirect(redirectStringBuaer.toString());
return;
}
}
}
}
%>
<html>
<head>
<title>Select your Single Sign-On Server</title>
</head>
<body>
<p style="text-align: center;"\>Select your SingleSign-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 buaer="5kb" autoFlush="true" session="false"%> <%@ page language="java"import="java.util.*,sun.misc.*,java.net.*"%>
<%response.setHeader("Expires", "Sat, 1 Jan 200000: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 contains 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
StringBuaer redirectStringBuaer = new StringBuaer(returnURL); if (returnURL.indexOf('?') == -1) redirectStringBuaer.append("?");
else if (returnURL.charAt(returnURL.length() - 1) != '&')
redirectStringBuaer.append("&"); redirectStringBuaer.append(returnIDParam); redirectStringBuaer.append("="); redirectStringBuaer.append(URLEncoder.encode(idp));
response.sendRedirect(redirectStringBuaer.toString());
%>
Packaging
Having put the landing.jsp
and submit.jsp
into a directory only containing those files, create the idpDiscService.war
WAR Ale 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 |
Then deploy 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 OAM/SP
To configure OAM/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 OAM/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 OAM/SP is invoked to start a Federation SSO, it redirects the user to my custom IdP Discovery Service (/idpdiscoveryservice/landing.jsp
)
On the first visit, the user is presented with a drop down list and prompted to select an IdP SSO Server:
Description of the illustration IdP_SSO_Server.jpg
Upon submitting the choice to /idpdiscoveryservice /submit.jsp
, the page validates the choice, save it into the IDPDiscService
cookie and redirect the user to OAM/SP with the IdP’s ProviderID
: from there, OAM/SP starts Federation SSO with that IdP.
The next time OAM/SP starts a Federation SSO for that user:
-
The server redirects the user to my custom IdP Discovery Service (
/idpdiscoveryservice/landing.jsp
) -
The page detects the
IDPDiscService
cookie and decode the IdP’sProviderID
-
The page redirects the user to OAM/SP with the IdP’s
ProviderID
-
OAM/SP starts Federation SSO with that IdP.
More Learning Resources
Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.
For product documentation, visit Oracle Help Center.
Implementing an IdP Discovery Service
F60448-01
September 2022
Copyright © 2022, Oracle and/or its affiliates.