JRun
Authentication
By Karl Moss
JRun Engineer
Allaire Corp.
This article explores the built-in authentication mechanisms
found in Allaire's JRun 3.0. Servlet/JSP authentication
was introduced in the Servlet API specification, version
2.2, which JRun 3.0 supports, and allows you to place authentication
and role-based access control (authorization) on any resource
delivered by the servlet container.
Servlet 2.2 Security
Chapter 11 of the Servlet API specification, version 2.2,
from Sun addresses security issues for Web applications. The
specification addresses two main points: authentication and
authorization. Let's first define these terms:
Authentication is the process of gathering user
credentials (user name and password) and validating them
in the system. This typically requires checking the credentials
against some user repository, such as a database or LDAP,
and authenticating that the user is who he or she says he
or she is.
Authorization is the process of making sure that
the authenticated user is allowed to view or access a given
resource. If a user is not authorized to view a resource,
the servlet container does not allow access.
From a Web application developer's point of view, a certain
collection of Web pages may be restricted to a certain type
of user, such as a manager. In the past, a Web developer
had to implement a custom security mechanism to collect
the user credentials and then verify that the user could
view the pages. Servlet containers that implement the Servlet
2.2 spec now handle these details for you. By placing security
constraints on a group of resources in the deployment descriptor
(web.xml), the servlet container will automatically gather
user credentials, authenticate the user, and authorize that
the user can view a given page.
Nuts and Bolts
As previously mentioned, authentication starts with defining
resource constraints in the deployment descriptor (web.xml).
The servlet specification defines the deployment descriptor
as the file that "conveys the elements and configuration information
of a Web application." Part of that configuration information
is not only "what" resources should have limited access, but
"how" that access is to be applied. Let's first take a look
at defining the "how."
The specification defines an auth-method
element, which tells the servlet container (JRun) how authentication
is to take place--in other words, what mechanism should
be used to gather the user credentials. The specification
defines four mechanisms:
BASIC This mechanism is an HTTP challenge/response
as defined in the HTTP/1.1 specification. The server responds
to the client with a status code of 401 (unauthorized),
and the client browser prompts for a user name and password.
After collecting the user name and password, the client
sends the information back to the server for authentication.
BASIC authentication is not a secure authentication protocol
because the user password is transmitted with a simple
Base64 encoding.
FORM You, the Web application developer,
create an HTML login form to be used to gather user name
and password information. Once complete the contents of
the form are submitted to the server for authentication.
FORM authentication is not secure either, because the
user password may be transmitted in clear text.
DIGEST Much like the BASIC mechanism, the
client browser prompts for a user name and password. Unlike
BASIC authentication, the user password is transmitted
in an encrypted manner, which is much more secure. Unfortunately,
digest authentication is not currently supported by all
browsers.
CLIENT-CERT Authenticates users using a
client certificate. This mechanism requires the user to
posses a Public Key Certificate (PKC).
Of these four mechanisms, JRun 3.0 current supports BASIC
and FORM. Note that if you are concerned about the security
risks of transmitting user password information, you can always
introduce additional protection by applying a secure transport
mechanism (HTTPS) or using security at the network level (such
as the IPSEC protocol or VPN).
The following is the relevant section of the deployment
descriptor (web.xml) for specifying BASIC authentication:
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>MySite</realm-name>
</login-config>
The realm-name element defines the name of the
security realm that will be used when the user is prompted;
if a realm-name is not specified, the realm-name
becomes the current Web application name. Again, the client
browser prompts for the user name and password; you have no
control over how the user information is gathered.
If you want to use FORM-based authentication, which gives
you complete control over how the login information is gathered,
the deployment descriptor must have the following elements:
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/loginpage.htm</form-login-page>
<form-error-page>/loginerror.htm</form-error-page>
</form-login-config>
</login-config>
Note that you specify the name of the login page and the name
of the error page if there are problems with authenticating
the user (such as a bad user name or password). The trick
here is that when creating the login page, you must adhere
to the naming conventions defined in the specification for
the form field names and the action. The user name must have
the name j_username, the password must have the
name j_password, and the action of the form must
be j_security_check. Here's a simple example:
<html>
<body>
<form method="post" action="j_security_check">
<input type=text name="j_username"> <br>
<input type=password name="j_password"><br>
<input type=submit>
</form>
</body>
</html>
This is a just a simple HTML form; you can jazz up the form
any way you want. Just remember that you must use the predefined
names, and you cannot currently gather any other type of information
(and have it recognized); just the user name and password.
Now that we've defined "how" users are to be authenticated,
let's now focus on "what" resources the users should have
limited access to. This is also defined in web.xml. You
can place what is called a security constraint upon any
number of resources and also specify what type of user (i.e.
a user in a particular role) has access to the resource:
<security-constraint>
<web-resource-collection>
<web-resource-name>Financial Reports</web-resource-name>
<url-pattern>/financials/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>manager</role-name>
</auth-constraint>
</security-constraint>
You can define any number of security-constraint
elements, each defining the Web resources that should have
limited accessibility. Part of the security-constraint
is the auth-constraint element, which defines
the role (or roles) that a user must be part of to gain access
to the resource. The user repository must include a list of
roles that each user belongs to so that the servlet container
can validate the roles defined in the security-constraint.
If the authenticated user is not in a role that has access
to that resource, the container will not allow the resource
to be viewed (JRun will return a status of 403 - Not Authorized).
| Note:
For further definitions and explanation
of servlet security, please refer to the
Servlet API version 2.2 (or higher) at http://java.sun.com/products/servlet
or the Developing Applications with JRun
manual (Chapter 38), which is installed
with JRun. |
|
Programming Security
in Your Servlets/JSPs
Perhaps placing security constraints on individual resources
does not give you the fine-grained control you would like.
You can also use the Servlet API to include calls in your
servlets/JSPs to determine who the current user is, and if
they belong to a given role. This allows you to create content
conditionally on the fly. The following methods are available
from HttpServletRequest:
string getRemoteUser();
boolean isUserInRole(String role);
Principal getUserPrincipal();
getRemoteUser returns the current
user name, or null if no user has been authenticated. isUserInRole
returns true if the current user is part of the given logical
role name (we'll visit what a logical role name in just a
bit). getUserPrincipal returns a java.security.Principal
object representing the current user, or null if no user has
been authenticated.
What is a logical role? The Servlet API allows you to
create a cross-reference of role names that you use programmatically
(in your servlet) to the role names stored in the user repository.
This cross reference is created in the deployment descriptor
(web.xml again) with the definition of the servlet. The
security-role-ref element allows you to define
the role name that is hard-coded in your servlet as well
as the actual role name used by the servlet container. This
may seem a bit odd, but it allows you to deploy a servlet,
without modification, into a container that uses different
role names (as long as you know what those role names are).
As an example, consider a servlet container that uses a
role name of "MGR" for managers and a servlet that checks
for the role of "manager." The servlet definition in web.xml
would contain:
<security-role-ref>
<role-name>manager</role-name>
<role-link>MGR</role-link>
</security-role-ref>
The servlet container must perform this cross-reference when
isUserInRole() is called:
if (request.isUserInRole("manager")) {
… add some logic for manager's only
}
Note that isUserInRole() will return false if
no user has been authenticated. It is also important to realize
that the methods in the API for programmatic security will
not cause a user to be authenticated. The authentication must
be initiated by the user attempting to access a resource with
a resource-constraint element.
Logging off
The Servlet specification does not address how to log users
off once they have been authenticated. It is safe to assume
that you can simply invalidate the current session to log
a user off, but if you want your session to remain intact,
you can remove the following JRun-specific attributes from
the current session:
allaire.jrun.security.authenticationState
allaire.jrun.security.principal
JRun 3.0 Custom Authentication
We have already explored how users are authenticated
and what resources should be restricted, but we did
not really touch on "where" user information is stored. The
Servlet specification does not address the physical attributes
of the user database, nor does it define how the servlet container
should access this information (whether it be file based,
JDBC, JNDI, etc.). JRun's default implementation is to store
the user information in a user property file, which can be
modified using any text editor. While this is a great way
to prototype applications, Allaire's JRun team realized that
not only is a more powerful authentication scheme necessary,
but also developers may already have a user repository that
they may want to "hook" JRun into. For these reasons, JRun
allows a custom authentication class to be plugged into the
server. This allows any type of user authentication to be
performed against any type of user repository that you may
have. Note that JRun also provides a custom authentication
mechanism that uses JDBC as well. Check Chapter 38 of Developing
Applications with JRun for more information.
JRun provides a simple Hashtable-based example implementation,
which can be found at docs/api/jrun/allaire/jrun/security/CustomAuthentication.html
(under the JRun install directory). You should be able to
read the fully annotated source code and implement your
own authentication class if necessary. Again, Chapter 38
of Developing Applications with JRun is a great reference
for creating your own authentication class.
If you are going to implement your own custom authentication
scheme, I have an implementation suggestion. It is not uncommon
to store additional information in a user repository, such
as personalization settings. Instead of rereading the user
repository each time you need this information, you should
consider extending allaire.jrun.security.AuthenticatedPrincipal
with your own class, store your custom information here,
and return an instance of this new class once a user has
been authenticated. Then, when you need access to your custom
information, you can simple call request.getUserPrincipal
in your servlet/JSP, cast the returned object to your new
class, and away you go!
About the author
Karl is a Principal Software Developer at Allaire Corporation
where he works on the JRun Application Server. Karl is also
the author of Java
Servlets (McGraw-Hill) and Java Database Programming with
JDBC (Coriolis), both of which are currently in their second
edition. When not writing Java code you may find him playing
his vintage video arcade game "Tutankham," for which he at
one time held the National high score.
|