Writing a Logon Exit
See also: Runtime
Authentication
Ebase provides support for customer-specific end-user
authentication via the logon exit mechanism. A logon exit is a Java servlet or JSP that authenticates an end-user. The logon
exit program can display pages to the end-user to perform the authentication if
necessary, or can use any other authentication mechanism.
As an alternative to the logon exit mechanism, Ebase
supports authentication performed by the application server. Application server
authentication is configured using the web.xml
deployment descriptor file and any User ID supplied by this mechanism is
extracted automatically by Ebase without the need for further configuration.
When application server authentication is used, the logon exit should normally
be specifically disabled by unchecking server
property Enable
Authentication of New Users.
The logon exit process was enhanced with Ebase V3.3 which
introduced support for runtime security authorizations.
Prior to V3.3, a logon exit was required to return a User
ID to Ebase. This User ID was then available using FPL as $USER and the
user was treated as authenticated. For the
remainder of this document, such a logon exit is referred to as old. Old
style logon exits are still supported in V3.3 and subsequent releases. Starting
from V3.3, when a User ID is returned by an old style logon exit, the Ebase
system will then call the configured UserManager to complete
the user – this entails adding any roles associated with the user; these roles
are in turn associated with authorizations and can be used to perform runtime
security checks. (See Ebase
Security Authentication for more information)
Starting from V3.3, a logon exit can alternatively return a
completed subject to Ebase. This subject represents both the User ID and
any roles associated with the user; these roles are in turn associated with
authorizations and can be used to perform runtime security checks. The logon
exit can call the configured UserManager to perform
this subject completion, or it can use any other mechanism it chooses.
The User ID is still made available to FPL as $USER as for old-style logon
exits. This style of logon exit is referred to as new in this document.
The following table shows the attributes of new and old
logon exits:
|
New |
Old |
Can be used with release |
From V3.3 |
All |
Returns |
User ID |
Subject |
Supports runtime authorizations |
Yes |
Yes |
Gets security roles from |
Anywhere |
UserManager |
Supports customizable logon pages |
Yes |
Yes |
In both cases, any security roles associated with the user
are saved in a subject, and the subject is itself
stored in the session. When a subsequent security authorization check is made,
the system calls the configured AuthorizationManager
to determine whether or not the user is authorized. The AuthorizationManager
is another configurable component where an Ebase-supplied implementation is
supplied, but this can be replaced if necessary.
The logon exit will receive control from Ebase when a new
session is encountered and server property Enable
Authentication of New Users is checked. For example:
The logon exit
class name is specified in the web.xml file
for the Ebase application e.g.
<servlet>
<servlet-name>LogonExit</servlet-name>
<display-name>LogonExit</display-name>
<servlet-class>MyLogonExit</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogonExit</servlet-name>
<url-pattern>/LogonExitServlet</url-pattern>
</servlet-mapping>
When a valid User ID has been determined, this must be
communicated to Ebase by saving the id in the session state attribute UFSUSERas shown
in the following code snippet:
import com.ebastech.ufs.kernel.Constants;
..............
HttpSession state = req.getSession(true);
state.setAttribute(Constants.SESSION_STATE_USER ID, User ID);
If a valid User ID cannot be determined, then value $?NO_USER?$ should be saved to indicate this as
shown below:
state.setAttribute(Constants.SESSION_STATE_USER ID, Constants.NO_USER);
Please note that an old style logon exit servlet is required to save a value in attribute
UFSUSER before returning to Ebase. Failure to do this can result in a loop
between Ebase and the logon exit.
To return to Ebase, the URL should be forwarded to the ufsmain servlet. The URL
must contain all parameters passed on the original request (this is not shown
in the example below).
RequestDispatcher disp = getServletConfig().getServletContext().getRequestDispatcher('/' + Constants.SERVLET_UFSMAIN);
disp.forward(req, resp);
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;
import com.ebasetech.ufs.kernel.Constants;
public class SampleLogonServlet extends HttpServlet
implements Servlet
{
public void
doGet(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException
{
process(req, resp);
}
public void
doPost(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException
{
process(req, resp);
}
private void
process(HttpServletRequest
req, HttpServletResponse
resp) throws ServletException, IOException
{
HttpSession state = req.getSession( true );
// Determine the User ID here...
String user = "Fred";
// Set the User ID in the session state
if (user == null) {
state.setAttribute(Constants.SESSION_STATE_USER ID, Constants.NO_USER);
}
else {
state.setAttribute(Constants.SESSION_STATE_USER ID, user);
}
// and return to Ebase
RequestDispatcher disp = getServletConfig().getServletContext().getRequestDispatcher(
'/' + Constants.SERVLET_UFSMAIN);
disp.forward(req, resp);
}
}
It is recommended that new-style logon exits extend class com.ebasetech.ufs.security.authentication.LogonExitServletBase
as shown in the example below. This base class provides many utility methods
required by a logon exit.
When a user has been authenticated, the logon exit must
create a javax.security.auth.Subject object and save
this in the Ebase session, as per the code snippet below:
//
address the Ebase session
initialiseEbaseSession(req);
//
Complete the subject using configured UserManager
UserManager auth = SecurityManager.Instance().getUserManager();
Subject
subject = new Subject();
try
{
auth.completeSubject(subject, user);
}
catch (AuthenticationException aue)
{
System.out.println("Authentication failed - " + aue.getMessage());
}
//
Save the subject in the Ebase session
saveSubjectInEbaseSession(subject);
The example above illustrates using the configured Ebase
User Manager. Alternatively, this could be achieved using an external
security system. For example:
//….. call
external system
//….. add UserPrincipal
(representing User ID)
Subject
subject = new Subject();
UserPrincipal userPrincipal =
new UserPrincipal(“Fred”);
subject.getPrincipals().add(userPrincipal);
//….. add RolePrincipal’s
(representing roles associated with the User ID)
Subject
subject = new Subject();
RolePrincipal rolePrincipal =
new RolePrincipal(“superuser”);
subject.getPrincipals().add(rolePrincipal);
Both UserPrincipal and RolePrincipal are in package com.ebasetech.ufs.security.authentication.
(See Ebase Security
Authentication for more information)
This example just shows the minimum code required. The
source for the Ebase logon exit is supplied in folder Ufs/samples.
public class SampleLogonExit
extends LogonExitServletBase implements Servlet
{
public static final String USERNAME = "e_username";
public static final String PASSWORD = "e_password";
public static final String PARM_LOGON_PAGE = "LogonPage";
public static final String PARM_INVALID_LOGON_PAGE =
"InvalidLogonPage";
public static final String DEFAULT_LOGON_PAGE =
"samples/logon/logon.jsp";
public static final String DEFAULT_INVALID_LOGON_PAGE
= "samples/logon/logonInvalid.jsp";
private String logonPage;
private String logonInvalidPage;
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
process(req, resp);
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
process(req, resp);
}
private void process(HttpServletRequest
req, HttpServletResponse resp) throws ServletException, IOException
{
try
{
// ensure no caching
setHTTPResponseHeader(resp);
// address the Ebase session
initialiseEbaseSession(req);
// First pass through....
if (isInitialCall(req))
{
clearInitialCallFlag(req);
// Check for illegal call to this servlet
if (!isRequestLegal(req, resp))
{
displayIllegalCallPage(req, resp);
return;
}
// This is a legal call so..
else
{
// Save the calling parameters for later return to Ebase
saveCallingParameters(req);
// Re-save Ebase session in session state - required for clustering
saveEbaseSession(req);
// Pass control to the logon page to start the authentication process.
linkToPage(req, resp, logonPage);
return;
}
}
// Subsequent pass, we have received control from the logon page, so check User
ID/password
else
{
String user = req.getParameter(USERNAME);
String password = req.getParameter(PASSWORD);
//Do authentication here...
boolean authenticationOK
= true;
// All OK
if (authenticationOK)
{
UserManager userManager = SecurityManager.Instance().getUserManager();
Subject subject = new Subject();
try
{
userManager.completeSubject(subject, user);
}
catch (AuthenticationException
aue)
{
System.out.println("Authentication failed - " + aue.getMessage());
return;
}
saveSubjectInEbaseSession(subject);
/* Clean up and return to Ebase */
saveEbaseSession(req);
returnToEbase(req, resp);
}
// Failed
else
{
clearEbaseSession(req);
clearCallingParameters(req);
clearInitialCallFlag(req);
linkToPage(req, resp, logonInvalidPage);
return;
}
}
}
catch (Throwable e)
{
Helper.logDesignerError("Unexpected error in logon exit " + e.getMessage());
e.printStackTrace();
}
}
public void init( ServletConfig
conf ) throws ServletException
{
super.init(
conf );
logonPage = conf.getInitParameter(PARM_LOGON_PAGE);
if (logonPage == null ) logonPage = DEFAULT_LOGON_PAGE;
logonInvalidPage = conf.getInitParameter(PARM_INVALID_LOGON_PAGE);
if (logonInvalidPage == null
) logonInvalidPage = DEFAULT_INVALID_LOGON_PAGE;
}
}