| CONTENTS | PREV | NEXT | INDEX | J2EE BluePrints |
This section describes the sample application's security requirements and discusses the ways these requirements are addressed using the J2EE security framework.
The pet store application is designed to be deployed on the Internet. Like many Web-based e-commerce applications, it allows anyone to interact with and use the application. Any user, regardless of whether they're a registered customer, can point a browser at the start URL of the application and browse through the catalog, viewing items, prices, inventory status, and so on. We call this tire kicking, and this class of users tirekickers.
A new customer can sign up using a form presented by the application. Once a customer has signed up, the customer can sign in, by providing a user name and password to the application. Only customers who have signed in are allowed to place orders and view order status. When an order is placed, the payment details, including the credit card number, must be transmitted in a secure manner.
Some users of the pet store application may receive special treatment. For example, a frequent shopper may a preferred customer, able to receive discounts or awards not available to normal customer. Another class of special user might be system administrators, with unlimited access to information on the site. For example, they might be able to fetch a list of all orders placed after a certain date.
These high-level functional requirements translate into the following security requirements:
User Authentication
Users of the pet store application can be either authenticated or unauthenticated. The user must be authenticated to access a protected resource. The application should be able to identify, differentiate, and be able to make access control decisions based on this distinction.
There should be a way to associate each authenticated user with one or more categories. For example, userAcould be recognized as a customer, while userBcould be recognized as a preferred customer.
Authorization
The application associates permissions with resources such as Web pages or enterprise bean methods. Examples of the kind of authorization constraints the application should allow:
| - | Anyone (authenticated or not), to see the URL /control/product, or invoke the getProducts method of Catalog |
| - | All authenticated users to see the URL /control/placeorder |
| - | Only preferred users to see the URL /control/discounts |
Confidentiality
Some user information, such as a credit card number, must be transmitted confidentially to the application.
User Administration
The sample application has its own set of users. This set of users grows when new users add themselves using a Web-based interface. Note that other applications, such as those developed for in-house use within an enterprise assume and use the set of users defined in the operational environment. The sample application does not depend on the operational environment to get its set of users.
The pet store application uses many features of the J2EE platform to address its security requirements in a simple and transparent manner. By design, security in the J2EE platform is mostly declarative. In some places however, we make security decisions in our components programmatically, because we needed to make authorization decisions based on the content or state of the object.
10.11.2.1 User Authentication
A J2EE application must be capable of authenticating users that access the application from a variety of clients. This section describes how the pet store application authenticates users of the shopping interaction Web client, how it could authenticate users of an administration application client, and how it handles unauthenticated users.
Most of the interactions with the sample application occur through the Web-based interface. Form-based authentication, one of the standard authentication mechanisms in the J2EE architecture, is used to authenticate these interactions.
In form-based authentication, a Web container designates an application-specific page containing an HTML form for logging in. The sample application uses the page login.jsp as this page. This page contains an HTML form that prompts for a user name and password and is displayed when the user tries to access a resource that has been designated as being protected. The sample application also uses form-based authentication to enable:
- Explicit signin
The sample application allows users to explicitly sign in by clicking the sign-in link in the user interface. The sign-in link points to/signinwhich is a dummy URL that is inaccessible to unauthenticated users.When the user clicks sign-in, the application attempts to take them to thesignin.jsppage, which is denied since the page is protected. As a result, thelogin.jspform is shown instead.Note that we cannot simply make the sign-in link point tologin.jspbecause an authorization failure must occur for the form-based authentication mechanism to be activated.- Informing the user about failed authentication
The sample application retries the protected resource after authentication through form-based authentication irrespective of the outcome of authentication. If authentication failed, thelogin.jspform will be shown to the user again. At this point, it is desirable to do two things: first, make sure that the form comes back already filled with the values that were posted in the last try, and second, inform the user somewhere in the form that authentication failed the first time.Form-based authentication does not provide a portable way to return the form to the user with the values posted in the failed try. ThePOSTto the form is handled by the Web container, and never returned to the form.Informing the user that signon failed is easier to accomplish. To do so, thelogin.jsppage uses a session-scoped bean (see Code Example 10.23) stored each time the form is accessed. If this time is close to the current time, and the current request is unauthenticated, then the sample application prints a message indicating that the login failed earlier. A request tologin.jspwould always be unauthenticated, unless there is an application programming error. The only situation wherelogin.jspis shown should be when the request is unauthenticated. Using a similar mechanism, it is also possible to go to an error page after a fixed number of retries.
<jsp:useBean id="last_login" class="...">
<% if (last_login.getTime() - currentTime < ... { %>
<font color="red">Login failed, try again<p></font>
<% } %>
Code Example 10.23 login.jsp
|
- Abandoning signin
Sometimes thelogin.jsppage comes up because the user tried to access a protected resource. If the user does not have an account and needs to create one, the application should abandon the login process and start user signup instead.The pet store application'slogin.jsppage has an additional button calledNewUser, to let new users sign up before they attempt to sign in.The J2EE platform maintains information about the state of the signin in theHttpSessionand times it out when the login attempt is abandoned.- Treating newly created users as signed in
When a new user signs up they should be treated as signed in for the duration of that session. This is the case for the sample application, which manages its own set of users. However, in the J2EE architecture, this is not the case. The only way users can sign in is through form-based authentication. Since form-based authentication is not invoked when a user signs up, they still need to explicitly sign in in order to be treated as an authenticated user.The sample application uses a non-portable, private API provided by the J2EE SDK to achieve the desired results.- Detecting user login
The form-based authentication mechanism is designed to be transparent. However there are cases where we want to be aware of the first request that the user makes after they have signed in. For example, in the sample application, the fetching and caching of user profile information is triggered when the user logs in.Since thePOSTto the login form is processed by the platform, there is no direct way of doing this. We use the code shown in Code Example 10.24.RequestProcessordetects when the user logs in and fires aLoginEventwhich can then be handled to get the desired effect.
private void checkForWebServerLogin(HttpServletRequest req) {
if ((req.getUserPrincipal() != null) &&
!req.getUserPrincipal().getName().equals("guest") &&
!mm.getAccountModel().isLoggedIn()) {
EStoreEvent loginEvent = null;
loginEvent = eventTranslator.createLoginEvent(req);
...
| Code Example 10.24 Triggering the Login Event |
Let's look at the condition being tested. We first check if the principal is set on the current call. If it is, it means that some user is currently logged in. Next we check if our account bean knows about it. IfaccountBean.isLoggedInreturns false, it means that the account bean is not aware of the login yet. This is exactly the condition when we want to trigger the login event. Once the login event is processed,account.isLoggedInwould return true.
In the J2EE platform, stand-alone clients are authenticated by an application client container. The application client may authenticate its user in a number of ways. The techniques used are platform-dependent and not under control of the application client. The application client container may integrate with the platform's authentication system, providing a single signon capability. The application client container may authenticate the user when the application is started. The application client container may use lazy authentication, only authenticating the user when it needs to access a protected resource. The J2EE specification does not describe the technique used to authenticate the user.
The J2EE SDK generates a client JAR file1 when enterprise beans are deployed. This library contains stub classes for accessing enterprise beans as well as a mechanism provided by the J2EE SDK for handling authentication to an EJB server.
The sample application allows for anonymous, unauthenticated users to access the application and browse selected features of the pet store. Even in such cases, calls to the EJB tier must specify a valid principal; the EJB container rejects all calls without a security principal. That is, if the user invokes a feature that tries to call the EJB tier without authentication, the EJB container will not let the call go through.
However, since the Web interface needs to support anonymous, unauthenticated users, the J2EE platform defines a mechanism to do so. The responsibility for ensuring that unauthenticated calls are made using some principal is delegated to the EJB client container. In the sample application, the Web container performs this role by:
- Associating the credentials of a special user called
guest2to an unauthenticated user when an EJB method is invoked.- Treating unauthenticated users as follows:
| - | The getUserPrincipal method of the servlet API returns null for such users. |
| - | The form-based or other authentication mechanism will be activated when a protected Web resource is accessed. |
10.11.2.2 Authorization
Sample application security is specified in terms of the security roles customer and gold_customer.
- A
customeris a registered user of the application. Users in thecustomerrole can place orders and complete purchases. In the current release of the sample application, the default userj2eeis in thecustomerrole.- A
gold_customeris a customer with special privileges. Additional awards are available to them. In the current release of the sample application, all users that sign up are assigned to thegold_customerrole.
By default, the J2EE SDK assigns the ANYONE role to an enterprise bean method. The guest user, which is anonymous and unauthenticated, is assigned to the ANYONE role.
In the sample application, access to the URLs /control/signin
(described in Section 10.11.2.1)
and /control/placeorder is restricted to the roles customer
and gold_customer. The security-constraint declaration
for /control/signin is shown in Code
Example 10.25.
<security-constraint> <web-resource-collection> ... <url-pattern>/control/signin</url-pattern> <http-method>POST</http-method> <http-method>GET</http-method> </web-resource-collection> <auth-constraint> <description>no description</description> <role-name>gold_customer</role-name> <role-name>customer</role-name> </auth-constraint> ... </security-constraint>
| Code Example 10.25 Security Constraint Declaration |
In the current release, the sample application does not limit enterprise bean method invocation to specific security roles.
10.11.2.3 Confidentiality
Confidentiality constraints are specified at deployment time by setting the transport-guarantee element in the Web component's deployment descriptor to CONFIDENTIAL. In the current release, the sample application doesn't demonstrate confidentiality mechanisms.
10.11.2.4 User Administration
Many applications will need to perform two tasks that aren't handled by the J2EE platform: managing user profile information (other than security credentials and attributes) and adding new users to the system dynamically.
In addition to keeping security credentials, the sample application needs other information about the user's preferences and personalization. The J2EE security framework will keep the security credentials, such as the user name and password, as well as attributes such as the set of roles that the user belongs to. The sample application needs another mechanism to maintain additional information for a user.
To do so, it maintains a separate relational table for user profile information.
This table is called the accounts table, and is accessed through
the Account enterprise bean. The user name is unique for each sample
customer, and we use it as a key to the accounts database. Code
Example 10.26 shows how the getCallerPrincipal method is used
to retrieve the userId of the user making the current enterprise
bean method call. The value returned from this method is used as a key to retrieve
profile information for the user.
public Account getAccount() {
if (acct == null) {
try {
String userId = sc.getCallerPrincipal().getName();
AccountHome home = EJBUtil.getAccountHome();
acct = home.findByPrimaryKey(userId);
} catch (FinderException fe) {
...
} catch (RemoteException re) {
throw new EJBException (re);
}
}
return acct;
}
Code Example 10.26 ShoppingClientControllerEJB.getAccount
|
The J2EE platform does not standardize a mechanism to add users dynamically to applications. Any application that requires this feature needs to do so in a non-portable, container-specific manner.
In such a situation, it makes sense to isolate all the non-portable code in one place. This small piece of platform-specific code can later be replaced if the application needs to be ported to a different container implementation.
The J2EE SDK provides a container-specific API for managing users based on the concept of realms. A realm is a collection of users under the same authentication policy. An application can provide its own realm and plug it into the J2EE SDK for the container to use for authentication, or it can use realm API methods such addUser, on the existing default J2EE realm.
The sample application uses the default J2EE realm. It uses the addUser method of the realm to add new users while processing the signup.jsp form.
In addition to specifying the user name and password of the user being added, we also need to specify the roles that this new user can assume. This is achieved through the addUser method, which takes an array of roles as an argument.
10.11.2.5 Programmatic Security
The J2EE platform encourages the use of declarative security. However there are places where one needs to make access control decisions based on the current state of the system. Such decisions must be made by programmatically encoding their rules in the application.
The J2EE platform allows the application to identify the principal making the call as well as the role that the caller is in, in both the Web and EJB tiers. The sample application uses these facilities as follows.
In the Web tier, the sample application uses the getUserPrincipal and the isUserInRole methods as follows:
getUserPrincipal: This method is used to get the ID of the user that connects to the application. This user ID could be used in the template to print the user ID in the banner as part of a welcome message. Another way that the sample application uses this information is to determine if a user is logged in. Code Example 10.24 illustrates this use.isUserInRole:In the current release, the sample application doesn't use this method. This method could be used in the template in order to show a different icon based on whether the user is a preferred or a regular customer. Additionally, there might be special items that we only show to preferred customers. Thus the catalog can be filtered based on the result returned from calling this method with thegold_customerrole.
In the EJB tier, the sample application uses getCallerPrincipal and isCallerInRole methods as follows:
getCallerPrincipal: This method is used to get the caller key to be able to access profile information associated with the principal associated with the call.isCallerInRole: This method is used by the order processing module to enforce award rules based on whether the customer is a preferred customer. Code Example 10.27 illustrates the use ofisCallerInRole.
private int getBonusMiles() {
int miles = (totalPrice >= 100) ? 1000 : 500;
if (context.isCallerInRole("GOLD_CUSTOMER"))
miles += 1000;
return miles;
}
Code Example 10.27 OrderEJB.getBonusMiles
|
Notice the use of the embedded role name GOLD_CUSTOMER. When role
names are embedded in the code, the Application Component Provider needs to
identify these roles in a deployment descriptor so that the Deployer can ensure
that they are mapped correctly when the application is deployed. Code
Example 10.28 shows the portions of the sample application deployment descriptor
where this happens.
<security-role-ref> <role-name>GOLD_CUSTOMER</role-name> <role-link>gold_customer</role-link> </security-role-ref> ... <assembly-descriptor> <security-role> <role-name>gold_customer</role-name> </security-role> ... </assembly-descriptor>
| Code Example 10.28 Deployment Descriptor Element for Embedded Roles |
In this excerpt from the deployment descriptor, the Application Component Provider declares the use of GOLD_CUSTOMER in the application using the security-role-ref element. The Deployer must ensure that this role is linked to the gold_customer security role.