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:
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() { // ... } }
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>
<a href="<%= request.getContextPath() %>/logout">log out</a>