Implementing Single Sign-on

In Seraph, user authentication is handled by an Authenticator class. Custom authenticators can be written to implement single sign-on in your apps. The default authenticator implementation in Seraph can be used to provide all the common code, and you just need to implement the two abstract methods for your specific use case.
For instance, your organization may have a single portal for logging in users, whose login page resides at http://mycompany.com/SSOLogin. Once the user logs in here, a cookie is set in the user's browser (typically encoding a username), indicating they are authenticated. Any application within the cookie's domain could check for the presence of that cookie, and automatically log the user into that application.
A slight complication is that if a user is not logged in and tries to access application's page, they must be redirected to the login page. Assuming successful authentication, they should be redirected to the original resource they requested.
Users should also be able to log out of the system. In other words, any "log out" link within an application should redirect to a global logout page (eg. http://mycompany.com/SSOLogout), which will kill the cookie and perform any other logout logic.
The system described above can be implemented in Seraph as follows:

Writing a custom Authenticator

To recap, our custom Seraph authenticator needs to check the user's request for the globally set cookie, and based on that, log the user in as the user encoded in the cookie. There is much application-specific logic here, which we will keep in a SSOnCookie object:

package com.mycompany.seraph;

import javax.servlet.http.HttpServletRequest;

public class SSOnCookie
{
    public static SSOnCookie getSSOCookie(HttpServletRequest request)
    {
        // ...
    }

    public boolean isExpired()
    {
        // ...
    }

    /** Return the username implied by the cookie in the request. */
    public String getLoginId()
    {
        // ...
    }
}
Assuming you have implemented this correctly, the following Seraph SSOAuthenticator will use this class to authenticate the user:
package com.mycompany.seraph;

import org.apache.log4j.Category;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.atlassian.seraph.auth.DefaultAuthenticator;

import java.security.Principal;

/**
 * Extension of DefaultAuthenticator that uses third-party code to determine if a user is logged in,
 * given a HTTPRequest object.
 * Third-party code will typically check for the existence of a special cookie.
 * 
 * In SSO scenarios where this authenticator is used, one typically configures Seraph to use an external login page
 * as well:
 * 
 *  <init-param>
 *    <param-name>login.url</param-name>
 *    <param-value>http://mycompany.com/globallogin?target=${originalurl}</param-value>
 *  </init-param>
 * 
* */ public class SSOAuthenticator extends DefaultAuthenticator { private static final Category log = Category.getInstance(SSOAuthenticator.class); public Principal getUser(HttpServletRequest request, HttpServletResponse response) { Principal user = null; try { if(request.getSession() != null && request.getSession().getAttribute(DefaultAuthenticator.LOGGED_IN_KEY) != null) { log.info("Session found; user already logged in"); user = (Principal) request.getSession().getAttribute(DefaultAuthenticator.LOGGED_IN_KEY); } else { SSOnCookie ssoCookie = SSOnCookie.getSSOCookie(request); log.info("Got SSOnCookie "+ssoCookie); if (ssoCookie != null && !ssoCookie.isExpired()) { // Seamless login from intranet log.info("Trying seamless Single Sign-on..."); String username = ssoCookie.getLoginId(); System.out.println("Got username = " + username); if (username != null) { user = getUser(username); log.info("Logged in via SSO, with User "+user); request.getSession().setAttribute(DefaultAuthenticator.LOGGED_IN_KEY, user); request.getSession().setAttribute(DefaultAuthenticator.LOGGED_OUT_KEY, null); } } else { log.info("SSOCookie is null; redirecting"); //user was not found, or not currently valid return null; } } } catch (Exception e) // catch class cast exceptions { log.warn("Exception: " + e, e); } return user; } }

The main logic is in the second-last 'else' statement, where we use SSOnCookie to get the username, and log the user into the application by setting a session attribute.
If the user isn't logged in (the last 'else' statement), we return null, which causes Seraph to redirect the user to the login URL. The login URL is set in seraph-config.xml:

	....
  <init-param>
    <!--
    the URL to redirect to for logging in.
    - if the URL is absolute (contains '://'), then redirect that URL (for SSO applications)
    - else the context path will be prepended to this URL

    If '${originalurl}' is present in the URL, it will be replaced with the URL that the user requested.
    This gives SSO login pages the chance to redirect to the original page
    -->
    <param-name>login.url</param-name>
    <param-value>http://mycompany.com/SSOLogin?target=${originalurl}</param-value>
  </init-param>
   ....

In our example, logout is handled by http://mycompany.com/SSOLogout. Our app could simply hardcode logout links with

<a href="http://mycompany.com/SSOLogout">log out</a>
, but a more flexible solution is to link to a Seraph logout servlet:
<a href="<%= request.getContextPath() %>/logout">log out</a>
. This extra level of indirection lets you completely configure login and logout behaviour through Seraph.