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 commenting the appropriate line in UFSSetup.properties as
follows:
#Ufs.logonExitServlet=LogonExitServlet
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 the logon exit option is enabled in UFSSetup.properties.
For example:
Ufs.logonExitServlet=LogonExitServlet
Where LogonExitServlet is a relative URL
that is mapped to a specific servlet 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;
}
}