Web and Enterprise Architecture Design Patterns for J2EE, Part 2
by Ganesh Prasad, Rajat Taneja and Vikrant Todankar
09/17/2003
Editor's Note: This is the second article in a series in which the authors propose patterns in the following five categories: Partitioning, Scope, Security, Navigation, and Data Volume Control. Part 1 contained descriptions of the first two categories. This week, the authors discuss Security, Navigation, and Data Volume Control.
Security Patterns
Most non-trivial web and enterprise applications have security requirements, and these fall
into the following areas:
- Privacy/Encryption: a means to prevent unauthorized viewing of information.
- Authentication: a means to verify that a user is who they say they are.
- Access Control/Authorization/Entitlements: a means to check if a user is allowed to perform an operation or access a resource.
- Message Integrity: a means to ensure that data is not tampered with in transit.
- Non-repudiation (a corollary of Authentication and Message Integrity): a means to ensure that the user cannot plausibly deny having sent a message.
The security subsystems of most applications will need to provide more than one of the
above mechanisms, and frequently all of them. Web applications do not generally
implement encryption and message integrity mechanisms themselves, because
the HTTPS/SSL technology incorporated into browsers and web servers is sufficient
to provide these.
Most of the time, developers spend time and effort building authentication and access control
subsystems, even though these features are ostensibly part of the J2EE specification.
The reason for this wheel reinvention is that the standard J2EE security mechanisms
are often inadequate for the purposes of many applications.
Standard authentication schemes typically return Boolean values to signify permission/denial, whereas
an application may require further information, such as whether this is a
first-time user, or whether the user's password has expired and must be changed.
Similarly, authorization tags in EJB deployment descriptors control access
to components, but are not fine-grained enough to enforce, for example, monetary
limits on transactions, an essential requirement of many financial applications.
In other words, most attempts to reinvent the security wheel at the application level are
aimed at going beyond the coarse-grained, black-or-white logic provided by
the J2EE container.
1. Access Context/Nuanced Response
Intent
To provide the fine-grained and nuanced security advice required by real-world applications, which J2EE does not offer.
Problem and Solution
Given the need for an arbitrarily fine-grained, multi-valued response to authentication,
session validation and access control requests, we propose a special design
pattern for such situations. In this pattern, any request to a subsystem providing
such services will be accompanied by a single parameter -- an "Access Context."
The Access Context object will, broadly speaking, hold information about
the user, the function they are attempting to perform, and an optional Value
Object representing the parameters to that function.
The subsystem that is called will then analyze these parameters and return a nuanced response that contains more information than just "yes" or "no."
This Nuanced Response wrapper object may contain a status code, an optional qualifier,
and an optional set of descriptions. The status code could be an unqualified
yes. It could also be one of several values representing an unqualified no, or it could be one of several values representing a qualified yes.
If the response is a qualified yes, the associated qualifier object will
contain more details, and the client is assumed to know how to interpret
these. The accompanying descriptions may similarly represent the set of all
status messages that led to this final status. Exact implementations may
vary in detail, but the outline solution is for all such calls to have a
similar signature, i.e., a single Access Context parameter and a returned
value that is a Nuanced Response.
Consequences
The positive consequence of the Access Context/Nuanced Response pattern is a standard and straightforward
mechanism that can be used without great effort once set up. It is capable
of delivering adequately fine-grained authentication, session validation and
access control for most applications.
The negative consequence of this pattern is the increased initial complexity of the solution. This complexity
can be mitigated by having experienced developers code the initial implementations of
its use, so that less-experienced developers can use them as templates.
2. Advisor
Intent
To encapsulate the logic required to answer complex security and state-management queries.
Problem and Solution
An Advisor is our term for the component that accepts an Access Context and returns a Nuanced
Response. The Advisor pattern, as we mentioned before, may be used for authentication,
session validation, access control, and other similar functions.
For example, a simple way to implement access control functionality would be to delegate
it to a specialized Advisor component that determines if a proposed action
is permissible or not. The pattern is called Advisor because it does not enforce
rules; it merely indicates what should be done by returning an arbitrarily
fine-grained Nuanced Response. It is up to the calling component to act on
the advice given. Trivial or non-critical business functions may perform
their logic without calling an access control Advisor, for example.
Consequences
The major positive consequence of the Advisor design pattern stems from its encapsulation of all verification
logic in a single component or subsystem, which makes it easier to maintain.
User roles, for example, can be managed entirely within an access control
Advisor without having to be exposed to any other part of the application.
An Advisor also acts as a facade for any rules engine it may use, allowing
the application to start with a set of simple rules and expand it into a more
intelligent mechanism as time goes by.
The negative consequence of this design pattern is that the application's rules are only enforced if
components call the Advisor and act upon its advice. If a developer neglects
to code a call to an access control Advisor at the start of a business function,
for example, then the function becomes freely accessible.
3. Interceptor
Intent
To enforce the application of security and state management rules.
Problem and Solution
The Interceptor pattern is meant to address the drawback of the Advisor pattern by taking
away the discretion of calling components to check and enforce rules. It uses
a reliable mechanism to always perform such checks before control is passed
to such a component. It is not a substitute for the Advisor pattern, but merely
an add-on to ensure that the advice of the Advisor is binding.
The new, red-hot technology known as Aspect-Oriented Programming (AOP) is an extremely elegant
way to implement the Interceptor pattern.
Consequences
Obviously, the main positive consequence of the Interceptor design pattern is the increased assurance that rules will be complied with. It also takes away responsibility for rule
enforcement from business components, making them simpler.
The negative consequences of the pattern may stem from the need to master new technologies that implement it well (such as AOP), although Interceptors can be coded using regular Java
programming techniques, as well. Another consequence is that discretionary
bypassing of access control checks (which may be justified in less critical
cases, for performance reasons) may be harder to implement.
4. Need to Know
Intent
To pre-emptively block access to unauthorized business functions.
Problem and Solution
There is a design requirement often referred to as "pre-emptive access control." The idea is
that if the user is not permitted to perform a certain action, the application
should not provide them with the button or menu item to trigger it in the
first place. They should only see what they can access.
The Need to Know design pattern is a kind of server-modified Client pattern, in which the server
determines the functions that the user may perform and only generates the
front-end components corresponding to those functions. (The process of determining
which functions are allowed may, in turn, use Advisor and Access Context/Nuanced
Response patterns.)
Sometimes, perhaps for marketing reasons, it may be required to display even functions that are
not accessible, albeit in a disabled form. (This is a way of saying, "See what
extra goodies you can access if you sign up for our Platinum membership?")
The Need to Know pattern can support such a requirement as well, when used
in conjunction with Advisor and Access Context/Nuanced Response.
Consequences
The positive consequence of the Need to Know design pattern is a more natural and
intuitive application. It locks the stable door before the security horse has bolted.
There is no real negative consequence to the pattern, unless the extra processing
required to suppress unauthorized navigation capability is deemed wasteful!
5. Gatekeeper
Intent
To provide a convenient facade for a set of cooperating security components.
Problem and Solution
The Gatekeeper is a complex Security pattern, which is why we have saved it for last.
It uses the Advisor, Access Context/Nuanced Response, and (optionally) Stateless
Channel patterns to create a comprehensive security framework for an application.
It is a specialized form of the GoF (the "Gang of Four": Gamma, Helms, Johnson, and Vlissides, authors of Design Patterns) Facade design pattern that is most useful
for channel-independent business logic, such as that implemented by EJB.
The basic idea is that the entire logic of the application tier is hidden behind a Gatekeeper
facade, which performs some coordination functions to simplify the design
of other components.
The first contact of a client with the server occurs in the form of an authentication request
to the Gatekeeper, which is the only externally visible component on the server.
The authentication request contains a single Access Context parameter. The
Gatekeeper consults an authentication Advisor, passing it the Access Context,
and receives a Nuanced Response in return. The login is either allowed or
rejected in an unqualified manner, or it is allowed in a qualified way (e.g.,
"First login -- change password", "Password expired -- force change", etc.).
If the response is an unqualified yes, the Gatekeeper then activates a State Management component
and gets it to generate a session token, which it then passes back to the
client as part of the Nuanced Response. Otherwise, it merely returns the original
Nuanced Response. Whenever it is called, the State Management component records
the session and its expiry time.
(Note that the session token can be managed using the Stateless Channel pattern from this
point on.)
Subsequent accesses from the client are to the Gatekeeper as before, but they now consist of requests
to business functions. All of the associated Access Context objects must now
contain the session token, in addition to the Value Object that holds the
parameters to the business function. The Gatekeeper consults a session validation
Advisor using this Access Context, and the Advisor returns a Nuanced Response
specifying whether the session is valid, invalid, or expired. The session
validation Advisor is just an interface implemented by the State Management
component. If the session is valid, the State Management component resets
the expiry time of the session to keep it alive.
If the session is valid, the Gatekeeper presses ahead with the business function; otherwise,
it returns the Nuanced Response forthwith to the client.
It is possible to implement an Interceptor pattern at this stage by having the Gatekeeper
consult an access control Advisor before deciding whether to call the business
function. If the Advisor says no, the Gatekeeper returns the Nuanced Response
to the client and doesn't call the business function at all.
Another option would be to call the business function straightaway and let it decide whether
to make the call to the Advisor or not.
Either way, the Gatekeeper acts as the go-between for the client and the business function,
and the access control Advisor is called some time before the business logic
is executed.
Note that the business functions can themselves implement the Advisor pattern, in that they
can advise the Gatekeeper (through a Nuanced Response) on whether they succeeded
or not, and if not, what kind of error or warning condition they encountered.
The Gatekeeper will then return the Nuanced Response object returned by the
business function. This is,
in fact, the most natural way to code the application's business functions,
once the Advisor/Access Context/Nuanced Response framework is in place.
In the J2EE context, the Gatekeeper and all Advisors are best implemented as Stateless Session
Beans. The Gatekeeper is the only EJB that will have a remote interface. Other
EJBs (security-related as well as business-related) will only have local
interfaces. This ensures that external clients can only see the Gatekeeper.
They can never directly access any other component. The security tags in the
deployment descriptor can be used to enforce the rule that all access to other
components can only occur through the Gatekeeper. The additional checks
performed by the Gatekeeper will provide further security around the application's
business functions. This combination of container-level and application-level
security can be very effective.
Consequences
We can see that interposing a Gatekeeper facade between clients (or channels) and application
logic makes the design much more modular by clearly delineating responsibility
for various security and business functions, and imposing a standard and consistent
calling convention. It is highly secure, and caters to all of the major security
concerns outlined at the beginning of this section (assuming that SSL is
used to ensure privacy and data integrity). This is the major positive consequence of
the Gatekeeper pattern.
The major negative consequence is that the relative complexity of this set of cooperating
components makes it overkill for simpler applications. But if an open source
implementation is readily available (we hope to provide one), it should be
relatively simple to incorporate it into any application and enjoy its substantial
benefits.