|
By Prashant Dighe, Aravindan Ranganathan, and Marina Sum, July 30, 2008
|
|
|
The Identity Services section in Part 1 of this series lists the Representational State Transfer (REST) operations that are supported by OpenSSO, Sun's open Web access management project. OpenSSO is based on the code base of Sun Java System Access Manager, a core identity-infrastructure software product. Here in Part 4, you're shown how to achieve single sign-on (SSO) and logout with REST. Even though the procedures in this part start with authentication as a key part of the process, the emphasis is on SSO and logout.
Note: In typical deployments, authentication is performed by a centralized server that can be distributed across multiple machines. All applications would then rely on that server for authentication and log in users with SSO. Another advantage offered by a centralized server is that it isolates the applications from authentication mechanisms, which range from the simple user-name/password credential scheme to complex approaches, such as multifactor authentication and federation.
Before proceeding, do read the first three parts of this series for the background on identity services and other related details:
As in Parts 1 through 3, we assume that you are familiar with the NetBeans IDE and that you have installed the latest build of OpenSSO. Afterwards, configure OpenSSO on the application server in which you will deploy the application. Feel free to deploy on another application server on another machine.
Contents
The following table lists the URLs, parameters, and responses of the two REST operations that relate to cookie names and the logout operation:
 |
Validating tokens |
http://localhost:8080/opensso/identity/isTokenValid
|
tokenid
|
boolean
|
Obtaining the Token cookie name |
http://localhost:8080/opensso/identity/getCookieNameForToken
|
|
cookieName
|
Obtaining all cookie names |
http://localhost:8080/opensso/identity/getCookieNamesToForward
|
|
cookieNames
|
Logging out |
http://localhost:8080/opensso/identity/logout
|
tokenid
|
|
This sample application, which is built on the NetBeans IDE, displays the user's subject ID and identity attributes. When the user accesses the application through its URL, the following scenarios apply:
- If the user is already logged in, the application enables SSO access for that person by verifying the SSO token with OpenSSO and then displays the user's subject ID and attribute.
- If the user is not yet logged in, the application redirects that person to the OpenSSO login page, that is, the centralized authentication server. Simultaneously, the application sends OpenSSO a
goto parameter, which contains the application's URL, so that OpenSSO can redirect the user back to the application after successful authentication.
In the NetBeans IDE, follow the 11 procedures in this section to create a Web application project, the related JSP pages, servlet, and methods.
Creating a Web Application Project
Create a standard Web application project named OSSOSample.
Creating a JSP Page
Next, create a protected.jsp file to generate a JSP page that displays the user's token ID and attributes:
<%@ taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c' %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Protected JSP Page</title>
</head>
<body>
<form name="sample" action="/OSSOSample/SampleServlet" method="post">
<br>
<b>SubjectId of user is</b> <c:out value="${subjectId}"/>
<br>
<br>
<h3>Authenticated user attributes are:</h3>
<table width="75%" cellspacing="2" cellpadding="2" border="1">
<tr>
<td><b>Attribute Name</b></td>
<td><b>Attribute Value</b></td>
</tr>
<c:forEach var="attr" items="${attrNameValues}">
<tr>
<td><c:out value="${attr.key}"/></td>
<td><c:out value="${attr.value}"/></td>
</tr>
</c:forEach>
</table>
<br>
<br>
<input name="logout" value="Logout" type="submit">
</form>
</body>
</html>
|
Adding a Servlet to the Project
Create a servlet named SampleServlet and add a private member, as follows, for simplicity. You can, of course, initialize the string value through the servlet's init parameters.
private String serviceUrl = "http://localhost:8080/opensso";
|
Later on, you will include this string in most of the methods.
Obtaining the Token Name
You must protect the cookie Token, which contains the user's authenticated identity, from impersonation by malicious hackers. Since REST sends Token as a GET parameter, plug that security hole: Send Token either as a POST operation or as a cookie.
Before sending Token, find out its name with a REST call, getCookieNameForToken, by adding a method to SampleServlet, as in the following code segment. Incidentally, this method shares the name of the cookie with which OpenSSO holds the user's SSO token.
private String getTokenCookieName() throws IOException {
String cookieName = null;
String url = serviceUrl + "/identity/getCookieNameForToken";
HttpURLConnection connection =
(HttpURLConnection)(new URL(url).openConnection());
BufferedReader br = new BufferedReader(
new InputStreamReader((InputStream)connection.getContent()));
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
String line = null;
while ((line = br.readLine()) != null) {
if (line.startsWith("string=")) {
line = line.replaceFirst("string=", "");
cookieName = line;
break;
}
}
}
return cookieName;
}
|
Obtaining the Other Cookie Names
In addition to Token, OpenSSO can also set other cookies, such as the Load Balancer cookie, that are to be replayed by REST. Since the cookie names stay the same during deployment, OpenSSO can cache all the cookies for optimal performance. Do take advantage of that capability.
Find out all the other cookie names with a getCookieNamesToForward call by adding this method to SampleServlet:>
private Set getCookieNamesToForward() throws IOException {
Set nameSet = new HashSet();
String url = serviceUrl + "/identity/getCookieNamesToForward";
HttpURLConnection connection =
(HttpURLConnection)(new URL(url).openConnection());
BufferedReader br = new BufferedReader(
new InputStreamReader((InputStream)connection.getContent()));
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
String line = null;
while ((line = br.readLine()) != null) {
if (line.startsWith("string=")) {
line = line.replaceFirst("string=", "");
nameSet.add(line);
}
}
}
return nameSet;
}
|
Forwarding the Cookies
Now forward all the cookies to OpenSSO by adding this method to SampleServlet:
private void forwardCookies(HttpServletRequest request,
HttpURLConnection connection, Set<String> cookieNames) {
StringBuilder sb = new StringBuilder();
Cookie[] cookies = request.getCookies();
cookies = cookies == null ? new Cookie[0] : cookies;
for(Cookie cookie : cookies) {
String cookieName = cookie.getName();
if(cookieNames.contains(cookieName)) {
String cookieValue = cookie.getValue();
sb.append(cookieName);
sb.append("=");
sb.append(cookieValue);
sb.append(";");
}
}
if (sb.length() > 0) {
connection.setRequestProperty("Cookie", sb.toString());
}
}
|
Verifying the User's Login Status
Next, find out if the user has logged in. One way to do that is to add the authenticate call described in the section "Making Authentication Calls With REST" in Part 1 of this series. Recall that if the user is not yet logged in, OpenSSO displays the login screen.
The following method returns true if the user has logged in; otherwise, it returns false. Add the method to SampleServlet.
private boolean isAuthenticated(HttpServletRequest request)
throws IOException {
boolean authenticated = false;
String url = serviceUrl + "/identity/isTokenValid";
HttpURLConnection connection =
(HttpURLConnection) (new URL(url).openConnection());
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-type",
"application/x-www-form-urlencoded");
forwardCookies(request, connection, getCookieNamesToForward());
OutputStreamWriter osw =
new OutputStreamWriter(connection.getOutputStream());
osw.write("dummy");
osw.flush();
osw.close();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream in_buf = connection.getInputStream();
StringBuffer inbuf = new StringBuffer();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(
in_buf, "UTF-8"));
while ((line = reader.readLine()) != null) {
inbuf.append(line).append("\n");
}
String data = new String(inbuf);
if (data.toLowerCase().indexOf("boolean=true") != -1) {
authenticated = true;
}
}
return authenticated;
}
|
Obtaining the User's Subject ID
Moving on, obtain from Token the user's subject ID (subjectid). Add the following method, which reads Token from the request and returns the value to SampleServlet.
private String getSubjectId(HttpServletRequest request)
throws IOException {
String subjectId = null;
String cookieName = getTokenCookieName();
Cookie[] cookies = request.getCookies();
for(Cookie cookie: cookies) {
String cName = cookie.getName();
if(cName.equals(cookieName)) {
subjectId = cookie.getValue();
break;
}
}
return subjectId;
}
|
Obtaining the User Attributes
The following method makes a REST call to the attributes operation and returns a map object (Map) in the form of name-value pairs for the user attributes. Add the method to SampleServlet so that the latter can display those attributes.
public Map<String, String> getAttributes(HttpServletRequest req)
throws IOException {
Map<String, String> nameValues = new HashMap<String, String>();
String url = serviceUrl + "/identity/attributes";
HttpURLConnection connection =
(HttpURLConnection)(new URL(url).openConnection());
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty(
"Content-type", "application/x-www-form-urlencoded");
forwardCookies(req, connection, getCookieNamesToForward());
OutputStreamWriter osw = new OutputStreamWriter(
connection.getOutputStream());
osw.write("dummy");
osw.flush();
osw.close();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(
new InputStreamReader((InputStream)connection.getContent()));
String line = null;
while ((line = br.readLine()) != null) {
if (line.startsWith("userdetails.attribute.name=")) {
String name = line.replaceFirst(
"userdetails.attribute.name=", "");
line = br.readLine();
if (line.startsWith("userdetails.attribute.value=")) {
String value = line.replaceFirst(
"userdetails.attribute.value=", "");
nameValues.put(name, value);
} else {
nameValues.put(name, "");
}
}
}
}
return nameValues;
}
|
Logging Out the User
The following method calls a REST operation, logout, to invalidate the active user session, causing a single logout. Add the method to SampleServlet.
private void logout(HttpServletRequest request)
throws IOException {
String url = serviceUrl + "/identity/logout";
HttpURLConnection connection =
(HttpURLConnection)(new URL(url).openConnection());
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-type",
"application/x-www-form-urlencoded");
forwardCookies(request, connection, getCookieNamesToForward());
OutputStreamWriter osw =
new OutputStreamWriter(connection.getOutputStream());
osw.write("dummy");
osw.flush();
osw.close();
connection.getResponseCode();
}
|
Calling the Methods
Finally, call the preceding methods in SampleServlet. When adding SampleServlet to the project, the NetBeans IDE defined a processRequest method, which is called from doGet and doPost. Replace the code for processRequest with the following:
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String logout = request.getParameter("logout");
if ("Logout".equals(logout)) {
logout(request);
}
if (isAuthenticated(request)) {
String subjectId = getSubjectId(request);
request.setAttribute("subjectId", subjectId);
Map<String, String> attrs = getAttributes(request);
request.setAttribute("attrNameValues", attrs);
RequestDispatcher dispatcher =
getServletContext().getRequestDispatcher("/protected.jsp");
dispatcher.forward(request, response);
} else {
response.sendRedirect(serviceUrl + "/UI/Login?goto=" +
request.getRequestURL().toSting();
}
}
|
To deploy and run the sample application in the NetBeans IDE, right-click the project and choose Undeploy and Deploy from the context menu. The IDE then compiles the necessary classes, builds the WAR file, and deploys it to your application server. Afterwards, you can access the client application at http://localhost:8080/OSSOSample/SampleServlet.
Since authentication has yet to occur, OpenSSO displays a login screen. Enter the user name and password. After successful authentication, you will be redirected back to the application, as shown in Figure 1.
Figure 1 Sample Application
Subsequent parts of this series will show you how to audit, create, and update identity services. Stay tuned!
- Sun Java System Access Manager
- OpenSSO
- Sun developer services
|
Prashant Dighe, a software engineer at Sun since 2000, has been working on various components of the Sun Java Enterprise System middleware stack. Among them are identity management, Sun Java System Portal Server, and integration of identity servicesSSO, identity-based content delivery, and Liberty federationinto Portal Server. Currently, he's focusing on the development of a next-generation Web aggregation and presentation platform called Project WebSynergy.
|
Aravindan Ranganathan, a software architect at Sun, is currently designing identity services for SOA. Previously, he developed solutions for securing Web-service communications and for implementing SSO within and across enterprises. Aravindan has also actively participated in standards bodies, such as the Liberty Alliance Project in defining the Identity Federation Framework (ID-FF); and OASIS Technical Committees in developing SAML, XACML, and SPML specifications.
|
Marina Sum is a staff writer for Sun Developer Network. She has been writing for Sun since 1989, mostly in the technical arena. Marina blogs on Sun's products, technologies, events, publications, and unsung heroes.
|
|