When designing Web-based applications, security is a critical
component. Before the advent of J2EE, to implement a secure
distributed application you had to code all of the security directly
into the application.
J2EE introduced a powerful security infrastructure for
applications that greatly assists developers and enterprises in
securing their applications. When used properly, this infrastructure
takes much of the burden of securing the application off of the
developers, leaving them free to concentrate on implementing business
logic.
The J2EE container-security services primarily address the
security requirements of authentication and authorization.
Authentication is the mechanism by which callers and service
providers prove to each other that they are acting on behalf of
specific users or systems. Authorization mechanisms provide control
over what resources an identified user or system has access to. In
simple terms, authentication provides the answer to "Who are you?"
And authorization provides the answer to "What can you access?"
Authentication
The J2EE model supports several methods for authenticating
users. These are basic, digest, form-based, and certificate-based
authentications. In basic authentication, the Web server prompts the
user for a user name and password, which is then transmitted to the
server. Unless an SSL session has been established, this information
is sent in the clear, so it's not very secure. Digest authentication
improves the security a bit by sending a digest of the user name and
password along with some session-specific information to the server
instead of transmitting the clear text password.
Both of these methods result in a standard dialog being
presented to the user for entering user name and password.
Form-based authentication allows the developer to create a
custom log-in page using a form. Like basic authentication, the user
name and password are sent in the clear, unless an SSL session has
been established.
Finally, the most secure method of authentication is the
certificate-based method. In this method, both client and server use
X.509 certificates to prove their identities. This authentication
always occurs over an SSL-protected channel.
The Web component deployment descriptor specifies which
resources are protected, thus requiring user authentication. The
actual step of authenticating the user is usually accomplished by
looking the user up in a corporate directory or database.
After successfully proving a user's or service's identity, an
authentication context is established. This allows the user or
service to be authenticated to other entities - without repeating the
authentication lookup step. A user may also delegate its
authentication context to a component, allowing that component to
call another component while impersonating the original caller.
The authentication mechanism is configured in the Web
component deployment descriptor. Listing 1 shows an example of
configuring digest authentication. Listing 2 shows an example of
configuring form-based authentication. The error page specified in
Listing 2 is a page presented to the user when authentication fails.
Authorization
J2EE employs a permissions-based authorization model. Each
protected resource is listed in the deployment descriptor, along with
a list of roles that are able to access the resource. These roles are
mapped to specific users by the application deployer. Static pages,
JSPs, and servlets are protected at the URL level and can be further
protected down to GET or POST methods. Protection of EJBs can be
specified down to specific-class methods. Specifying authorization
information in the deployment descriptor is referred to as
declarative authorization. Embedding authorization logic directly into an application is called
programmatic authorization. The J2EE model supports both of these
authorization models. Unless requirements demand it, declarative
authorization is generally the preferred method.
Declarative Authorization
The authorization rules specified in the deployment
descriptor are enforced by the J2EE container. This method of
providing authorization frees the developer from worrying about
implementing authorization, which is generally a good thing because
it's an easy area for developers to introduce bugs that can lead to
large security holes in the application. Applications that rely
solely on this form of declarative authorization are sometimes
referred to as security-unaware applications. The application code
itself has no knowledge of the security that is wrapping it.
Declarative authorization is a container-managed security service.
Listing 3 shows an example of applying declarative
authorization to a servlet in the deployment descriptor. In this
example, all servlets and other resources in the "restricted"
directory will be protected. Only users assigned the role
"AuthorizedUser" will be granted access to these resources. Listing 4
shows declarative authorization being applied to an EJB. This example
applies protection to the UserInformation bean. Users assigned the
"admin" role are granted rights to access all methods. Users
assigned the "customer" role are granted the right to access the
getDetails() method.
Programmatic Authorization
The J2EE model also provides support for extending the
authorization model through programmatic authorization. There are
four key methods developers can use. They are:
- isCallerInRole() and getCallerPrinciple() for use by EJB code
- isUserInRole() and getUserPrinciple() for use by Web components
These methods are typically used to provide finer-grain
access control than what can be achieved through pure declarative
authorization. Applications that employ programmatic authorization
are referred to as security-aware applications. Programmatic security
is also referred to as application-managed security.
A good example of where programmatic authorization is
necessary is in protecting access to user account objects. Consider
an application that manages some type of user accounts.
Let's say the developer designates two roles: one for users
and the other for administrators. The users should have access only
to their own account. The administrators should have access to all
accounts. There is no way to enforce this policy using declarative
authorization alone. This stems from the fact that access rules
specified in the deployment descriptor are class-based, not
instance-based. Specific users or roles can't be granted access to
specific instances of an object while denying them access to other
instances of an object.
A solution is to use programmatic authorization. In the
account object, the developer would use the isUserInRole() method to
determine if the user was an administrator. If so, the user would
automatically be granted access to the account. If not, the developer
would then use the method getUserPrinciple() and compare that to the
account owner. If they are the same, access would be granted.
Implementing security inside each application greatly
increases the chance of introducing a vulnerability that can be
exposed by hackers. Programmatic authorization should be used only
when the requirements can't be met through declarative authorization.
Confidentiality and Integrity
So far, I've covered the topics of authentication and
authorization. The other important security requirement is message
protection, specifically, employing the concepts of confidentiality
and integrity.
Integrity ensures that a message hasn't been accidentally or
intentionally modified. Confidentiality ensures the privacy of a
message. Both of these services are provided through cryptographic techniques.
The J2EE security model doesn't provide a great deal of
flexibility or support in this area. In the deployment descriptor,
resources can be designated as requiring confidentiality. This
permits them to be served only over SSL connections. Going beyond the
pure J2EE security model, there's a great deal of support in Java for
providing programmatic protection of messages.
Divided Responsibilities
One of the goals of the J2EE security model is to lessen the
burden on the application developer for securing applications. To
assist in achieving this goal, the J2EE model recognizes the three
distinct roles played in bringing an application from concept to
deployment. The roles are component provider, application assembler,
and deployer.
Slight variations on these role names appear in various
publications. The component provider is the developer, the person
actually writing the Java code. The application assembler takes
several components (beans, EJBs, JSPs, etc.) and assembles them into
a complete application. Finally, the deployer is responsible for
actually deploying the application into the production enterprise
environment.
The component provider implements all programmatic security.
In doing so, they define generic security role names for providing
access control functionality. The application assembler defines
logical roles, specifies which resources should be protected, and
which roles are required to access those resources. The deployer then
is responsible for mapping the logical roles to the specific
enterprise environment. It should be noted that one individual or
team may play multiple roles in bringing an application to
production. It's not unusual for the component provider and the
application assembler roles to be performed by the same team.
Container-Managed Security
Implementing modern security mechanisms such as
confidentiality, authentication, and authorization requires a great
deal of expertise in the security domain. Most application developers
don't have the expertise needed to successfully implement these
security services. This makes creating applications that are
security-aware all the more challenging.
A better idea is to rely on the application-server vendors
to implement the tough security services - and have your application
make use of these container-managed services. This eliminates the
necessity of having a security development engineer on every Web
application project.
Having said this, the requirements must be the ultimate
driver for whether or not programmatic security should be used. Some
requirements, such as the account example given in this article,
can't be satisfied using container-managed security alone. It should
be noted that this example and the methods provided by J2EE
specifically focus on authorization. It's best to always rely on
container-managed authentication. Embedding authentication logic into
each application creates a hard to manage authentication policy that
has a high potential for security vulnerabilities.
Another advantage of relying on containermanaged services is
that as authentication technology is upgraded within an enterprise,
the applications don't have to be rewritten. Using programmatic
authentication would require massive application rewrites if
enterprise authentication technology were to change.
Relying predominantly on container-managed security also
allows you to maintain a common security infrastructure as opposed to
sprinkling security throughout all your Web applications.
Container-managed security services allow for the centralization of
security policies in a central repository. When using
container-managed security, a change in security policy requires
changes in application configuration, as opposed to coding changes in
your applications.
Author Bio
Timothy Fisher is a consultant for Spherion Technology in Detroit,
and has been involved in information-security technology for nearly 10 years. He worked in information security at Motorola, Cyclone Commerce, and Pricewaterhouse
Coopers.
tim@securitydeveloper.com
Listing 1
<web-app>
<login-config>
<auth-method>BASIC|DIGEST</auth-method>
<realm-name>test</realm-name>
</login-config>
</web-app>
Listing 2
<web-app>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>login.jsp</form-login-page>
<form-error-page>error.jsp</form-error-page>
</form-login-config>
</login-config>
</web-app>
Listing 3
<web-app>
..
<security-constraint>
<web-resource-collection>
<web-resource-name>
Secure Content
</web-resource-name>
<url-pattern>/restricted/*</ url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>AuthorizedUser</role-name>
</auth-constraint>
</security-constraint>
..
<security-role>
<description>
The role required to access restricted content
</description>
<role-name>AuthorizedUser</role-name>
</security-role>
</web-app>
Listing 4
<method-permission>
<role-name>admin</role-name>
<method>
<ejb-name>UserInformation</ejb-name>
<method-name>*</method-name>
</method>
</method-permission>
<method-permission>
<role-name>customer</role-name>
<method>
<ejb-name>UserInformation</ejb-name>
<method-name>getDetails</method-name>
</method>
</method-permission>