Chapter 8. Security

In this chapter, we will cover how to secure Java EE applications by taking advantage of GlassFish's built-in security features. Java EE security relies on the Java Authentication and Authorization Service (JAAS) API. As we shall see, securing Java EE applications requires very little coding; for the most part, securing an application is achieved by setting up users and security groups in a security realm in the application server, then configuring our applications to rely on a specific security realm for authentication and authorization.

Some of the topics we will cover include:

  • The Admin realm

  • The File realm

  • The Certificate realm

    • Creating self-signed security certificates

  • The JDBC realm

  • Custom Realms

Security Realms

Security realms are, in essence, collections of users and related security groups. Users are application users. A user can belong to one or more security group; the groups that the user belongs to define what actions the system will allow the user to perform. For example, an application can have regular users who can only use the basic application functionality, and it can have administrators who, in addition to being able to use basic application functionality, can add additional users to the system.

Security realms store user information (user name, password, and security groups); applications don't need to implement this functionality, they can simply be configured to obtain this information from a security realm. A security realm can be used by more than one application.

Predefined Security Realms

GlassFish comes preconfigured with three predefined security realms: admin-realm, the file realm, and the certificate realm. admin-realm is used to manage user's access to the GlassFish web console and shouldn't be used for other applications. The file realm stores user information in a file. The certificate realm looks for a client-side certificate to authenticate the user.

Predefined Security Realms

In addition to the predefined security realms, we can add additional realms with very little effort. We will cover how to do this later in this chapter, but first let's discuss GlassFish's predefined security realms.

admin-realm

To illustrate how to add users to a realm, let's add a new user to admin-realm. This will allow this additional user to log in to the GlassFish web console. In order to add a user to admin-realm, log in to the GlassFish web console, expand the Configuation node at the left-hand side, then expand the Security node, then the Realms node, and click on admin-realm. The main area of the page should look like the following screenshot:

admin-realm

To add a user to the realm, click on the button labeled Manage Users at the top left. The main area of the page should now look like this:

admin-realm

To add a new user to the realm, simply click on the New... button at the top left of the screen. Then enter the new user information.

admin-realm

In the above screenshot, we added a new user named "root", added this user to the "asadmin" group, and entered this user's password.

Note

The GlassFish web console will only allow users in the "asadmin" group to log in. Failing to add our user to this security group would prevent him/her from logging in to the console.

admin-realm

We have successfully added a new user for the GlassFish web console. We can test this new account by logging into the console with this new user's credentials.

The file Realm

The second predefined realm in GlassFish is the file realm. This realm stores user information encrypted in a text file. Adding users to this realm is very similar to adding users to admin-realm. We can add a user by expanding the Configuration node, then expanding the Security node, then the Realms node, then clicking on file, then clicking on the Manage Users button and clicking on the New... button.

The file Realm

As this realm is meant for us to use for our applications, we can come up with our own groups. In this example, we added a user with a User ID of "peter" to the groups "appuser" and "appadmin".

Clicking the OK button should save the new user and take us to the user list for this realm.

The file Realm

Clicking the New... button allows us to add additional users to the realm. Let's add an additional user called "joe" and belonging only to the "appuser" group.

The file Realm

As we have seen in this section, adding users to the file realm is very simple. We will now illustrate how to authenticate and authorize users via the file realm.

File Realm Basic Authentication

In the previous section, we covered how to add users to the file realm and how to assign roles to these users. In this section, we will illustrate how to secure a web application so that only properly authenticated and authorized users can access it. This web application will use the file realm for user access control.

The application will consist of a few very simple JSPs. All authentication logic is taken care of by the application server, therefore the only place we need to make modifications in order to secure the application is in its deployment descriptors, web.xml and sun-web.xml. We will first discuss web.xml, which is shown next.

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Pages</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>All Pages</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>file</realm-name></web-app>

The<security-constraint> element defines who can access pages matching a certain URL pattern. The URL pattern of the pages is defined inside the<url-pattern> element, which, as shown in the example, must be nested inside a<web-resource-collection> element. Roles allowed to access the pages are defined in the<role-name> element, which must be nested inside an<auth-constraint> element.

In the above example, we define two sets of pages to be protected. The first set of pages is any page whose URL starts with /admin. These pages can only be accessed by users with the role of admin. The second set of pages is all pages, defined by the URL pattern of /*. Only users with the role of user can access these pages. It is worth noting that the first set of pages is a subset of the second set, that is, any page whose URL matches /admin/* also matches /*; in cases like this the most specific case "wins". In this particular case, users with a role of user (and without the role of admin) will not be able to access any page whose URL starts with /admin.

The next element we need to add to web.xml in order to protect our pages is the<login-config> element. This element must contain an<auth-method> element that defines the authorization method for the application. Valid values for this element include BASIC, DIGEST, FORM, and CLIENT-CERT.

BASIC indicates that basic authentication will be used. This type of authentication will result in a browser-generated popup, prompting the user for a user name and password, being displayed the first time a user tries to access a protected page. Unless using the HTTPS protocol, when using basic authentication, the user's credentials are Base64 encoded, not encrypted. It would be fairly easy for an attacker to decode these credentials; therefore using basic authentication is not recommended.

DIGEST is similar to basic authentication except it uses an MD5 DIGEST to encrypt the user credentials instead of sending them Base64 encoded.

FORM uses a custom HTML or JSP page containing an HTML form with user name and password fields. The values in the form are then checked against the security realm for user authentication and authorization. Unless using HTTPS, user credentials are sent in clear text when using form-based authentication, therefore using HTTPS is recommended because it encrypts the data. We will cover setting up GlassFish to use HTTPS, later in this chapter.

CLIENT-CERT uses client-side certificates to authenticate and authorize the user.

The<realm-name> element of<login-config> indicate which security realm to use to authenticate and authorize the user. In this particular example, we are using the file realm.

All of the web.xml elements we have discussed in this section can be used with any security realm; they are not tied to the file realm. The only thing that ties our application to the file realm is the value of the<realm-name> element. Something else to keep in mind is that not all authentication methods are supported by all realms. The file realm supports only basic and form-based authentication.

Before we can successfully authenticate our users, we need to link the user roles defined in web.xml with the groups defined in the realm. We accomplish this in the sun-web.xml deployment descriptor.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN" "http://www.sun.com/software/
appserver/dtds/sun-web-app_2_5-0.dtd">
<sun-web-app>
<security-role-mapping>
<role-name>admin</role-name>
<group-name>appadmin</group-name>
</security-role-mapping>
<security-role-mapping>
<role-name>user</role-name>
<group-name>appuser</group-name>
</security-role-mapping></sun-web-app>

As can be seen in the example, the sun-web.xml deployment descriptor can have one or more<security-role-mapping> elements; one of these elements for each role defined in web.xml is needed. The<role-name> subelement indicates the role to map. Its value must match the value of the corresponding<role-name> element in web.xml. The<group-name> subelement must match the value of a security group in the realm used to authenticate users in the application.

In this example, the first<security-role-mapping> element maps the "admin" role defined in the application's web.xml deployment descriptor to the "appadmin" group we created when adding users to the file realm earlier in the chapter. The second<security-role-mapping> maps the "user" role in web.xml to the "appuser" group in the file realm.

As we mentioned earlier, there is nothing we need to do in our code in order to authenticate and authorize users. All we need to do is modify the application's deployment descriptors as described in this section. As our application is nothing but a few simple JSPs, we will not show the source code for them. The structure of our application is shown in the following screenshot:

File Realm Basic Authentication

Based on the way we set up our application in the deployment descriptors, users with a role of "user" will be able to access the two JSPs at the root of the application (index.jsp and random.jsp). Only users with the role of "admin" will be able to access any pages under the "admin" folder, which in this particular case is a single JSP named index.jsp.

After packaging and deploying our application and pointing the browser to the URL of any of its pages, we should see a popup asking for a user name and a password.

File Realm Basic Authentication

After entering the correct user name and password, we are directed to the page we were attempting to see.

File Realm Basic Authentication

At this point, the user can navigate to any page he or she is allowed to access in the application, either by following links or by typing the URL in the browser, without having to re-enter his/her user name and password.

Notice that we logged in as user joe; this user belongs only to the user role, therefore he does not have access to any page with a URL that starts with /admin. If joe tries to access one of these pages, he will see the following error message in the browser.

File Realm Basic Authentication

Only users belonging to the admin role can see pages that match the above URL. When we were adding users to the file realm, we added a user named peter that had this role. If we log in as peter, we will be able to see the requested page. For basic authentication, the only way possible to log out of the application is to close the browser, therefore to log in as peter we need to close and reopen the browser.

File Realm Basic Authentication

As we mentioned before, one disadvantage of the basic authentication method we used in this example is that login information is not encrypted. One way to get around this is to use the HTTPS (HTTP over SSL) protocol; when using this protocol all information between the browser and the server is encrypted.

The easiest way to use HTTPS is by modifying the application web.xml deployment descriptor.

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Pages</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint></security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>AllPages</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint></security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>file</realm-name>
</login-config>
</web-app>

As we can see, all we need to do to have the application be accessed only through HTTPS is to add a<user-data-constraint> element containing a nested<transport-guarantee> element to each set of pages we want to encrypt traffic. Sets of pages to be protected are declared in the<security-constraint> elements in the web.xml deployment descriptor.

Now, when we access the application through the (unsecure) HTTP port (by default this is 8080), the request is automatically forwarded to the (secure) HTTPS port (default of 8181).

In this example, we set the value of the<transport-guarantee> to CONFIDENTIAL. This has the effect of encrypting all the data between the browser and the server, also, if the request is made through the unsecured HTTP port, it is automatically forwarded to the secured HTTPS port.

Another valid value for the<transport-guarantee> element is INTEGRAL. When using this value, the integrity of the data between the browser and the server is guaranteed; in other words, the data cannot be changed in transit. When using this value, requests made over HTTP are not automatically forwarded to HTTPS; if a user attempts to access a secure page via HTTP when this value is used, the browser will deny the request and return a 403 (Access Denied) error.

The third and last valid value for the<transport-guarantee> is NONE. When using this value, no guarantees are made about the integrity or confidentiality of the data. NONE is the default value used when the<transport-guarantee> element is not present in the application's web.xml deployment descriptor.

After making the above modifications to the web.xml deployment descriptor, redeploying the application and pointing the browser to any of the pages in the application, we should see the following.

File Realm Basic Authentication

The reason we see this warning window is that, in order for a server to use the HTTPS protocol, it must have an SSL certificate. Typically, SSL certificates are issued by certificate authorities such as Verisign or Thawte. These certificate authorities digitally sign the certificate; by doing this they certify that the server belongs to the entity to which it claims to belong.

A digital certificate from one of these certificate authorities typically costs around $400 USD, and expires after a year. As the cost of these certificates may be prohibitive for development or testing purposes, GlassFish comes preconfigured with a self-signed SSL certificate. As this certificate has not being signed by a certificate authority, the browser pops up the above warning window when we try to access a secured page via HTTPS. We can simply click OK to accept the certificate.

Once we accept the certificate, we are prompted for a user name and password; after entering the appropriate credentials, we are allowed access to the requested page.

File Realm Basic Authentication

Notice the URL in the above screenshot; the protocol is set to HTTPS, and the port is 8181. The URL we pointed the browser to was http://localhost:8080/filerealmauthhttps/random.jsp; because of the modifications we made to the application's web.xml deployment descriptor, the request was automatically forwarded to this URL. Of course, users may directly type the secure URL and it will work without a problem.

Any data transferred over HTTPS is encrypted, including the user name and password entered at the pop-up window generated by the browser. Using HTTPS allows us to safely use basic authentication. However, basic authentication has another disadvantage, which is that the only way that a user can log out from the application is to close the browser. If we need to allow users to log out of the application without closing the browser, we need to use form-based authentication.

When using form-based authentication, we need to make some modifications to the application's web.xml deployment descriptor.

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Pages</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>AllPages</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method><realm-name>file</realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/loginerror.jsp</form-error-page>
</form-login-config></login-config>
<servlet>
<servlet-name>LogoutServlet</servlet-name>
<servlet-class>
net.ensode.glassfishbook.LogoutServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogoutServlet</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
</web-app>

When using form-based authentication, we simply use FORM as the value of the<auth-method> element in web.xml. When using this authentication method, we need to provide a login page and a login error page. We indicate the URLs for the login and login error pages as the values of the<form-login-page> and<form-error-page> elements, respectively. As can be seen in the example, these elements must be nested inside the<form-login-config> element.

The markup for the login page for our application is shown next.

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Login</title>
</head>
<body>
<p>Please enter your username and password to access the application</p>
<form method="POST" action="j_security_check"><table cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="right">Username:&nbsp;</td>
<td>
<input type="text" name="j_username"></td>
</tr>
<tr>
<td align="right">Password:&nbsp;</td>
<td>
<input type="password" name="j_password"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Login"></td>
</tr>
</table>
</form>
</body>
</html>

The login page for an application using form-based authentication must contain a form whose method is"POST" and whose action is"j_security_check". We don't need to implement a servlet or anything else to process this form. The code to process it is supplied by the application server.

The form in the login page must contain a text field named j_username; this text field is meant to hold the user's user name. Additionally, the form must contain a password field named j_password, meant for the user's password. Of course, the form must contain a submit button to submit the data to the server.

The only requirement for a login page is for it to have a form whose attributes match those in the preceding example, and the j_username and j_password input fields as described in the above paragraph.

There are no special requirements for the error page. Of course, it should show an error message telling the user that login was unsuccessful; however, it can contain anything we wish. The error page for our application simply tells the user that there was an error logging in, and links back to the login page to give the user a chance to try again.

In addition to a login page and a login error page, we added a servlet to our application. This servlet allows us to implement logout functionality, something that wasn't possible when we were using basic authentication.

package net.ensode.glassfishbook;
import java.io.IOException;
import javax.servlet.ServletException;
file realmauthenticatingimport javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LogoutServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
request.getSession().invalidate();
response.sendRedirect("index.jsp");
}
}

As you can see, all we need to do to log out the user is invalidate the session. In our servlet, we redirect the response to the index.jsp page; as the session is invalid at this point, the security mechanism will "kick in" and automatically direct the user to the login page.

We are now ready to test form-based authentication; after building our application, deploying it, and pointing the browser to any of its pages, we should see our login page rendered in the browser.

File Realm Basic Authentication

If we submit invalid credentials, we are automatically forwarded to the login error page.

File Realm Basic Authentication

We can click on the Try again link to try again. After entering valid credentials, we are allowed into the application.

File Realm Basic Authentication

As you can see, we added a logout link to the page; this page directs the user to the logout servlet, which as we mentioned before simply invalidates the session. From the user's point of view, this link will simply log them out and direct them to the login screen.

The certificate Realm

The certificate realm uses client-side certificates for authentication. Just like server-side certificates, client side certificates are typically obtained from a certificate authority like Verisign or Thawte. These certificate authorities verify that the certificate really belongs to the entity to which it claims to belong.

Obtaining a certificate from a certificate authority costs money and takes some time. It might not be practical to obtain a certificate from one of the certificate authorities when we are developing and or testing our application. Fortunately, we can create self-signed certificates for testing purposes.

Creating Self-Signed Certificates

We can create self-signed certificates with little effort using the keytool utility included with the Java Development Kit.

Note

We will only briefly cover some of the keytool utility functionality; specifically, we will cover what is necessary to create and import self-signed certificates into GlassFish and into the browser. To learn more about the keytool utility, refer to http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/keytool.html.

Generating a self-signed certificate can be accomplished by typing the following command in the command line:

keytool -genkey -v -alias selfsignedkey -keyalg RSA -storetype PKCS12 -keystore client_keystore.p12 -storepass wonttellyou -keypass wonttellyou

The above command assumes that the keytool utility is in the system PATH. This tool can be found under the bin directory, under the directory where the Java Development Kit is installed.

Substitute the values for the -storepass and -keypass parameters with your own password; both of these passwords must be the same in order to successfully use the certificate to authenticate the client. You may choose any value for the -alias parameter. You may also choose any value for the -keystore parameter; however, the value must end in .p12, as this command generates a file that needs to be imported into the web browser, and this file won't be recognized unless it has the p12 extension.

After entering this command from the command line, keytool will prompt for some information.

What is your first and last name?
[Unknown]: David Heffelfinger
What is the name of your organizational unit?
[Unknown]: Book Writing Division
What is the name of your organization?
[Unknown]: Ensode.net
What is the name of your City or Locality?
[Unknown]: Fairfax
What is the name of your State or Province?
[Unknown]: Virginia
What is the two-letter country code for this unit?
[Unknown]: US
Is CN=David Heffelfinger, OU=Ensode.net, O=Book Writing Division, L=Fairfax, ST=Virginia, C=US correct?
[no]: y
Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days
for: CN=David Heffelfinger, OU=Ensode.net, O=Book Writing Division, L=Fairfax, ST=Virginia, C=US
[Storing client_keystore.p12]

After you enter the data for each prompt, keytool will generate the certificate; it will be stored in the current directory. The name of the file will be the value we used for the -keystore parameter (client_keystore.p12 in the example).

To be able to use this certificate to authenticate ourselves, we need to import it into the browser. The procedure, although similar, varies from browser to browser. In Firefox, this can be accomplished by going to Edit|Preferences, then clicking on the Advanced icon at the top of the resulting pop-up window, then clicking on the Encryption tab.

Creating Self-Signed Certificates

We then need to click on the View Certificates button, click on the Import button on the resulting window, then navigate and select our certificate from the directory in which it was created. At this point, Firefox will ask us for the password used to encrypt the certificate; in our example, we used wonttellyou as the password. After entering the password, we should see a pop-up window confirming that our certificate was successfully imported. We should then see it in the list of certificates.

Creating Self-Signed Certificates

We have now added our certificate to Firefox so that it can be used to authenticate ourselves. If you are using another web browsers, the procedure will be similar. Consult your browser's documentation for details.

The certificate we created in the previous step needs to be exported into a format that GlassFish can understand:

keytool -export -alias selfsignedkey -keystore client_keystore.p12 -storetype PKCS12 -storepass wonttellyou -rfc -file selfsigned.cer

The values for the -alias, -keystore, and -storepass parameters must match the values used in the previous command. You may choose any value for the -file parameter, but it is recommended for the value to end in the .cer extension.

As our certificate was not issued by a certificate authority, GlassFish by default will not recognize it as a valid certificate. GlassFish knows what certificates to trust based on the certificate authority that created them. The way this is implemented is that certificates for these various authorities are stored in a keystore named cacerts.jks. This keystore can be found in the following location:

[glassfish installation directory]/glassfish/domains/domain1/config/cacerts.jks.

In order for GlassFish to accept our certificate, we need to import it into the cacerts keystore. This can be accomplished by issuing the following command from the command line:

keytool -import -file selfsigned.cer -keystore [glassfish installation directory]/glassfish/domains/domain1/config/cacerts.jks -keypass changeit -storepass changeit

At this point, keytool will display the certificate information in the command line and ask us if we want to trust it.

Owner: CN=David Heffelfinger, OU=Book Writing Division, O=Ensode.net, L=Fairfax, ST=Virginia, C=US
Issuer: CN=David Heffelfinger, OU=Book Writing Division, O=Ensode.net, L=Fairfax, ST=Virginia, C=US
Serial number: 464f452f
Valid from: Sat May 19 14:42:55 EDT 2007 until: Fri Aug 17 14:42:55 EDT 2007
Certificate fingerprints:
MD5: A9:22:8E:2D:A3:06:BB:09:47:A7:02:E3:17:86:A2:6B
SHA1: 16:E2:85:BC:BF:19:77:D8:02:49:31:22:FE:A8:3A:D8: A7:3F:62:03
Signature algorithm name: SHA1withRSA
Version: 3
Trust this certificate? [no]: y
Certificate was added to keystore

Once we add the certificate to the cacerts.jks keystore, we need to restart the domain for the change to take effect.

What we are effectively doing here is adding ourselves as a certificate authority that GlassFish will trust. This of course should not be done in a production system.

The value for the -file parameter must match the value we used for this same parameter when we exported the certificate.

Note

"changeit" is the default password for the -keypass and -storepass parameters for the cacerts.jks keystore. This value can be changed by issuing the following command:

[glassfish installation directory]/glassfish/bin/asadmin change-master-password --savemasterpassword=true

This command will prompt for the existing master password and for the new master password. The –savemasterpassword=true parameter is optional; it saves the master password into a file called master-password in the root directory for the domain. If we don't use this parameter when changing the master password, then we will need to enter the master password every time we want to start the domain.

Now that we have created a self-signed certificate, imported it into our browser, and established ourselves as a certificate authority that GlassFish will trust, we are ready to develop an application that will use client-side certificates for authentication.

Configuring Applications to Use the Certificate Realm

As we are taking advantage of Java EE 5 security features, we don't need to modify any code at all in order to use the certificate realm. All we need to do is modify the application's configuration in its deployment descriptors, web.xml and sun-web.xml.

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http:// java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<security-constraint>
<web-resource-collection>
<web-resource-name>AllPages</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>users</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>certificate</realm-name>
</login-config></web-app>

The main difference between this web.xml deployment descriptor and the one we saw in the previous section is the contents of the<login-config> element. In this case, we declared CLIENT-CERT as the authorization method and certificate as the realm to use to authenticate. This will have the effect of GlassFish asking the browser for a client certificate before allowing a user into the application.

When using client-certificate authentication, the request must always be done via HTTPS, therefore; it is a good idea to add the<transport-guarantee> element with a value of CONFIDENTIAL to the web.xml deployment descriptor. Recall from the previous section that this has the effect of forwarding any requests through the HTTP port to the HTTPS port. If we don't add this value to the web.xml deployment descriptor, any requests through the HTTP port will fail because client-certificate authentication cannot be done through the HTTP protocol.

Notice that we declared that only users in the role of users can access any page in the system; we did this by adding the role of users to the<role-name> element nested inside the<auth-constraint> element of the<security-constraint> element in the web.xml deployment descriptor. In order to allow access to authorized users, we need to add them to this role. This is done in the sun-web.xml deployment descriptor.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd">
<sun-web-app>
<security-role-mapping>
<role-name>users</role-name>
<principal-name>CN=David Heffelfinger, OU=Book Writing Division, O=Ensode.net, L=Fairfax, ST=Virginia, C=US</principal-name>
</security-role-mapping></sun-web-app>

This assignment is done by mapping the principal (user) to a role in a<security-role-mapping> element in the sun-web.xml deployment descriptor; its<role-name> subelement must contain the role name, and the<principal-name> subelement contains the user name. This user name is taken from the certificate.

If you are not sure of the name to use, this name can be obtained from the certificate with the keytool utility.

keytool -printcert -file selfsigned.cer
Owner: CN=David Heffelfinger, OU=Book Writing Division, O=Ensode.net, L=Fairfax, ST=Virginia, C=US
Issuer: CN=David Heffelfinger, OU=Book Writing Division, O=Ensode.net, L=Fairfax, ST=Virginia, C=US
Serial number: 464f452f
Valid from: Sat May 19 14:42:55 EDT 2007 until: Fri Aug 17 14:42:55 EDT 2007
Certificate fingerprints:
MD5: A9:22:8E:2D:A3:06:BB:09:47:A7:02:E3:17:86:A2:6B
SHA1: 16:E2:85:BC:BF:19:77:D8:02:49:31:22:FE:A8:3A:D8:A7:3F:62:03
Signature algorithm name: SHA1withRSA
Version: 3

The value to use as<principal-name> is the line after Owner:. Please note that the value of<principal-name> must be in the same line as its opening and closing tags (<principal-name> and</principal-name>); if there are newline or carriage return characters before or after the value, they are interpreted as being part of the value and validation will fail.

As our application has a single user and a single role, we are ready to deploy it. If we had more users we would have to add additional<security-role-mapping> elements to our sun-web.xml deployment descriptor, at least one per user. If we had users that belong to more than one role, then we would add a<security-role-mapping> element for each role to which the user belongs, using the<principal-name> value corresponding to the user's certificate for each one of them.

We are now ready to test our application; after we deploy it and point the browser to any page in the application, we should see a screen like the following (assuming the browser hasn't been configured to provide a default certificate any time a server requests one):

Configuring Applications to Use the Certificate Realm

After clicking the OK button, we are allowed to access the application.

Configuring Applications to Use the Certificate Realm

Before allowing access to the application, GlassFish checks the certificate authority that issued the certificate (as we self-signed the certificate, the owner of the certificate and the certificate authority are the same), and checks against the list of trusted certificate authorities. Because we added ourselves as a trusted authority by importing our self-signed certificate into the cacerts.jks keystore, GlassFish recognizes the certificate authority as a valid one. It then gets the principal name from the certificate and compares it against entries in the application's sun-web.xml; because we added ourselves to this deployment descriptor and gave ourselves a valid role, we are allowed into the application.

Defining Additional Realms

In addition to the three pre-configured security realms we discussed in the previous section, we can create additional realms for application authentication. We can create realms that behave exactly like the file or admin-realm realms; we can also create realms that behave like the certificate realm. Additionally, we can create realms that use other methods of authentication. We can authenticate users against an LDAP database; we can also authenticate users against a relational database, and when GlassFish is installed on a Solaris server, we can use Solaris authentication within GlassFish. Also, if none of the above authentication mechanisms fits our needs, we can implement our own.

Defining Additional File Realms

Expand the Configuration node, expand the Security node, click on the Realms node, then click on the New... button on the resulting page in the main area of the web console. We should now see a screen like the following:

Defining Additional File Realms

All we need to do to create an additional realm is enter a unique name for it in the Name field, pick com.sun.enterprise.security.auth.realm.file.FileRealm for the Class Name field (should be the default), and enter a value for the Key File field; the value for this field must be the absolute path to a file where user information will be stored. The JAAS context field will default to fileRealm; this default should not be changed.

After entering all of the above information, we can click on the OK button and our new realm will be created. We can then use it just like the predefined file realm. Applications wishing to authenticate against this new realm must use its name as the value of the<realm-name> element in the application's web.xml deployment descriptor.

Defining Additional Certificate Realms

To define an additional certificate realm, we simply need to enter its name in the Name field and pick com.sun.enterprise.security.auth.realm.certificate.CertificateRealm as the value of the Class Name field, then click OK to create our new realm.

Defining Additional Certificate Realms

Applications wishing to use this new realm for authentication must use its name as the value of the<realm-name> element in the web.xml deployment descriptor, and specify CLIENT-CERT as the value of its<auth-method> element. Of course, client certificates must be present and configured as explained in the Configuring Applications to Use the Certificate Realm section.

Defining an LDAP Realm

We can easily set up a realm to authenticate against an LDAP (Lightweight Directory Access Protocol) database. In order to do this we need, in addition to the obvious step of entering a name for the realm, to select com.sun.enterprise.security.auth.realm.ldap.LDAPRealm as the Class Name value for a new realm.

Defining an LDAP Realm

We then need to enter a URL for the directory server in the Directory field, and the Base Distinguished Name (DN) to be used to search user data as the value of the Base DN field.

After creation of an LDAP realm, applications can use it to authenticate against the LDAP database. The name of the realm needs to be used as the value of the<realm-name> element in the application's web.xml deployment descriptor; the value of the <auth-method> element must be either BASIC or FORM. Users and roles in the LDAP database can be mapped to groups in the application's sun-web.xml deployment descriptor, using the<principal-name>, <role-name>, and<group-name> elements as discussed earlier in this chapter.

Defining a Solaris Realm

When GlassFish is installed in a Solaris server, it can "piggyback" on the operating system authentication mechanism via a Solaris Realm. There are no special properties for this type of realm, all we need to do to create one is pick a name for it and select com.sun.enterprise.security.auth.realm.solaris.SolarisRealm as the value of the Class Name field.

Defining a Solaris Realm

The JAAS context field will default to solarisRealm; this default should not be changed. After addition of the realm, applications can authenticate against it using basic or form-based authentication. Operating-system groups and users can be mapped to application roles defined in the application's web.xml deployment descriptor via the<principal-name>, <role-name>, and<group-name> elements in its sun-web.xml deployment descriptor.

Defining a JDBC Realm

Another type of realm we can create is a JDBC realm. This type of realm uses user information stored in database tables for user authentication.

In order to illustrate how to authenticate against a JDBC realm, we need to create a database to hold user information.

Defining a JDBC Realm

Our database consists of three tables. A USERS table holding user information, a GROUPS table holding group information, and as there is a many-to-many relationship between USERS and GROUPS, we need to add a join table to preserve data normalization. The name of this table is USER_GROUPS.

Notice that the PASSWORD column of the USERS table is of type CHAR(32). The reason we chose this type instead of VARCHAR is that by default, the JDBC realm expects passwords to be encrypted as an MD5 hash, and these hashes are always 32 characters.

Passwords can be easily encrypted in the format expected by default, by using the java.security.MessageDigest class included with the JDK. The following example code will take a clear text password and create an encrypted MD5 hash out of it.

package net.ensode.glassfishbook;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class EncryptPassword
{
public static String encryptPassword(String password)
throws NoSuchAlgorithmException
{
MessageDigest messageDigest =
MessageDigest.getInstance("MD5");
byte[] bs;
messageDigest.reset();
bs = messageDigest.digest(password.getBytes());
StringBuilder stringBuilder = new StringBuilder();
//hex encode the digest
for (int i = 0; i < bs.length; i++)
{
String hexVal = Integer.toHexString(0xFF & bs[i]);
if (hexVal.length() == 1)
JDBC realm{
stringBuilder.append("0");
}
stringBuilder.append(hexVal);
}
return stringBuilder.toString();
}
public static void main(String[] args)
{
String encryptedPassword = null;
try
{
if (args.length == 0)
{
System.err.println("Usage: java " +
"net.ensode.glassfishbook.EncryptPassword " +
"cleartext");
}
else
{
encryptedPassword = encryptPassword(args[0]);
System.out.println(encryptedPassword);
}
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
}
}

The "meat" of this class is its encryptPassword() method. It basically takes a clear text string and digests it using the MD5 algorithm by using the digest() method of an instance of java.security.MessageDigest. It then encodes the digest as a series of hexadecimal numbers. The reason this encoding is necessary is because GlassFish, by default, expects MD5 digested passwords to be hex encoded.

When using JDBC realms, the Glassfish users and groups are not added to the realm via the GlassFish console; instead, they are added by inserting data into the appropriate tables.

Once we have the database that will hold user credentials in place, we are ready to create a new JDBC realm.

We can create a JDBC realm by entering its name in the Name field of the New Realm form in the GlassFish web console, then selecting com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm as the value of the Class Name field.

Defining a JDBC Realm

There are a number of other properties we need to set for our new JDBC realm.

Defining a JDBC Realm

The JAAS context field will default to jdbcRealm for JDBC realms; this default should not be changed. The value of the JNDI property must be the JNDI name of the data source corresponding to the database that contains the realm's user and group data. The value of the User Table property must be the name of the table that contains user-name and password information.

Note

Notice, in the screenshot that, that we used V_USER_GROUPS as the value for this property. V_USER_GROUPS is a database view that contains both user and group information. The reason we didn't use the USERS table directly is because GlassFish assumes that both the user table and the group table contain a column containing the user name. Doing this results in having duplicate data. To avoid this situation, we created a view that we could use as the value of both the User Table Property and the Group Table Property (to be discussed shortly).

The User Name property must contain the column in the User Table that contains the user names. The Password property value must be the name of the column in the User Table that contains the user's password. The value of the Group Table property must be the name of the table containing user groups. The Group Name property must contain the name of the column in the Group Table containing user group names.

All other properties are optional and, in most cases, left blank. Of special interest is the Digest property. This property allows us to specify the message digest algorithm to use to encrypt the user's password. Valid values for this property include all algorithms supported by the JDK; these algorithms are MD2, MD5, SHA-1, SHA-256, SHA-384, and SHA-512. Additionally, if we wish to store user passwords in clear text, we can do so by using the value "none" for this property.

Once we have defined our JDBC realm, we need to configure our application via its web.xml and sun-web.xml deployment descriptors. Configuring an application to rely on a JDBC realm for authorization and authentication is done just as when using any other type of realm.

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http:// java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Pages</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>AllPages</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<realm-name>jdbc</realm-name><form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/loginerror.jsp</form-error-page>
</form-login-config>
</login-config>
<servlet>
<servlet-name>LogoutServlet</servlet-name>
<servlet-class>
net.ensode.glassfishbook.LogoutServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogoutServlet</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
</web-app>

In the above example, we set the value of the<realm-name> element in the web.xml deployment descriptor to jdbc; this is the name we chose to give our realm when we configured it through the GlassFish console.

In this example, we chose to use form-based authentication, but we could have used basic authentication instead.

In addition to declaring that we will rely on the JDBC realm for authentication and authorization, just as with other types of realms, we need to map the roles defined in the web.xml deployment descriptor to security group names. This is accomplished in the sun-web.xml deployment descriptor.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd">
<sun-web-app>
<security-role-mapping>
<role-name>admin</role-name>
<group-name>Admin</group-name>
</security-role-mapping>
<security-role-mapping>
<role-name>user</role-name>
<group-name>Users</group-name>
</security-role-mapping></sun-web-app>

The value of the<role-name> elements must match the corresponding<role-name> elements in web.xml. The value of<group-name> must be a value in the column specified by the Group Name Column property of the JDBC realm, as specified when it was configured in the GlassFish web console.

Defining Custom Realms

The predefined realm types should cover the vast majority of cases. However, we can create custom realm types if the predefined ones don't meet our needs. Doing so involves coding custom Realm and LoginModule classes. Let's first discuss the custom realm class.

package net.ensode.glassfishbook;
import java.util.Enumeration;
import java.util.Vector;
import com.sun.enterprise.security.auth.realm.IASRealm;
import com.sun.enterprise.security.auth.realm.InvalidOperationException;
import com.sun.enterprise.security.auth.realm.NoSuchUserException;
public class SimpleRealm extends IASRealm
{
@Override
public Enumeration getGroupNames(String userName)
throws InvalidOperationException, NoSuchUserException
{
Vector vector = new Vector();
vector.add("Users");
vector.add("Admin");
return vector.elements();
}
@Override
public String getAuthType()
{
return "simple";
}
@Override
public String getJAASContext()
{
return "simpleRealm";
}
public boolean loginUser(String userName, String password)
{
boolean loginSuccessful = false;
if ("glassfish".equals(userName) &&
"secret".equals(password))
{
loginSuccessful = true;
}
return loginSuccessful;
}
}

Our custom realm class must extend com.sun.enterprise.security.auth.realm.IASRealm; this class can be found inside the appserv-rt.jar file; therefore this JAR file must be added to the CLASSPATH before our Realm can be successfully compiled.

Note

appserv-rt.jar can be found under [glassfish installation directory]/glassfish/lib.

Our class must override a method called getGroupNames(). This method takes a single String as a parameter and returns an Enumeration. The String parameter is for the user name for the user that is attempting to log into the realm. The Enumeration must contain a collection of Strings indicating what groups the user belongs to. In our simple example, we basically hard-coded the groups. In a real application, these groups would be obtained from some kind of persistent storage (database, file, etc.).

The next method our realm class must override is the getAuthType() method. This method must return a String containing a description of the type of authentication used by this realm.

The above two methods are declared as abstract in the IASRealm (parent) class. Though the getJAASContext() method is not abstract, we should nevertheless override it, because the value it returns is used to determine the type of authentication to use from the application server's login.conf file. The return value of this method is used to map the realm to the corresponding login module.

Finally, our realm class must contain a method to authenticate the user; we are free to call it anything we want; additionally, we can use as many parameters of any type as we wish. Our simple example simply has the values for a single user name and password hard-coded; again a real application would obtain valid credentials from some kind of persistent storage. This method is meant to be called from the corresponding login module class.

package net.ensode.glassfishbook;
import java.util.Enumeration;
import javax.security.auth.login.LoginException;
import com.sun.appserv.security.AppservPasswordLoginModule;
import com.sun.enterprise.security.auth.realm.InvalidOperationException;
import com.sun.enterprise.security.auth.realm.NoSuchUserException;
public class SimpleLoginModule extends
AppservPasswordLoginModule
{
@Override
protected void authenticateUser() throws LoginException
{
Enumeration userGroupsEnum = null;
String[] userGroupsArray = null;
SimpleRealm simpleRealm;
if (!(_currentRealm instanceof SimpleRealm))
{
throw new LoginException();
}
else
{
simpleRealm = (SimpleRealm) _currentRealm;
}
if (simpleRealm.loginUser(_username, _password))
{
try
{
custom realmsuserGroupsEnum = simpleRealm.getGroupNames(_username);
}
catch (InvalidOperationException e)
{
throw new LoginException(e.getMessage());
}
catch (NoSuchUserException e)
{
throw new LoginException(e.getMessage());
}
userGroupsArray = new String[2];
int i = 0;
while (userGroupsEnum.hasMoreElements())
{
userGroupsArray[i++] =
((String) userGroupsEnum.nextElement());
}
}
else
{
throw new LoginException();
}
commitUserAuthentication(userGroupsArray);
}
}

Our login module class must extend the com.sun.appserv.security.AppservPasswordLoginModule class which is also inside the appserv-rt.jar file; it only needs to override a single method, authenticateUser(). This method takes no parameters and must throw a LoginException if user authentication is unsuccessful. The _currentRealm variable is defined in the parent class; it is of type com.sun.enterprise.security.auth.realm.Realm, the parent class of all realm classes. This variable is initialized before the authenticateUser() method is executed. The login module class must verify that this class is of the expected type (SimpleRealm in our example); if it is not, a LoginException must be thrown.

Another two variables that are defined in the parent class and initialized before the authenticateUser() method is executed are _username and _password; these variables contain the credentials that the user entered in the login form (for form-based authentication) or pop-up window (for basic authentication). Our example simply passes these values to the realm class so that it can verify the user credentials.

The authenticateUser() method must call the commitUserAuthentication() method of the parent class upon a successful authentication. This method takes an array of String objects containing the group the user belongs to. Our example simply invokes the getGroupNames() method defined in the realm class and adds the elements of the Enumeration it returns to an array, then passes that array to commitUserAuthentication().

Obviously, GlassFish is unaware of the existence of our custom realm and login module classes. We need to add these classes to GlassFish's CLASSPATH; the easiest way to do this is through the web console.

Defining Custom Realms

We can add directories and JAR files to GlassFish's CLASSPATH by clicking on the Application Server node, clicking on the JVM Settings tab, then clicking on the Path Settings sub-tab, then entering the path of the folder or JAR file in the text area labeled Classpath Suffix. The domain needs to be restarted for this change to take effect.

The last step we need to follow before we can authenticate applications against our custom realm is to add our new custom realm to the domain's login.conf file.

/* Copyright 2004 Sun Microsystems, Inc. All rights reserved. */
/* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */
fileRealm {
com.sun.enterprise.security.auth.login.FileLoginModule required;
};
ldapRealm {
com.sun.enterprise.security.auth.login.LDAPLoginModule required;
};
solarisRealm {
com.sun.enterprise.security.auth.login.SolarisLoginModule required;
};
jdbcRealm {
com.sun.enterprise.security.auth.login.JDBCLoginModule required;
};
simpleRealm {
net.ensode.glassfishbook.SimpleLoginModule required;
};

The value before the opening brace must match the return value of the getJAASContext() method defined in the realm class. It is in this file that the realm and login module classes are linked to each other. The GlassFish domain needs to be restarted for this change to take effect.

We are now ready to use our custom realm to authenticate users in our applications. We need to add a new realm of the type we created via GlassFish's admin console.

Defining Custom Realms

To create our realm, as usual we need to give it a name. Instead of selecting a class name from the dropdown, we need to type it into the text field. Our custom realm didn't have any properties, therefore we don't have to add any in this example. If it did, they would be added by clicking on the Add Property button and entering the property name and corresponding value. Our realm would then get the properties by overriding the init() method from its parent class. This method has the following signature:

protected void init(Properties arg0) throws
BadRealmException, NoSuchRealmException

The instance of java.util.Properties that it takes as a parameter would be pre-populated with the properties entered in the page shown in the previous screenshot (our custom realm doesn't have any properties, but for those that do, properties are entered in this page).

Once we have added the pertinent information for our new custom realm, we can use it just as we use any of the predefined realms. Applications need to specify its name as the value of the<realm-name> element of the application's web.xml deployment descriptor. Nothing out of the ordinary needs to be done at the application level.

Summary

In this chapter, we covered how to use GlassFish's default realms to authenticate our web applications. We covered the file realm, which stores user information in a flat file, and the certificate realm, which requires client-side certificates for user authentication.

Additionally, we covered how to create additional realms that behave just like the default realms by using the realm classes included with GlassFish.

We also covered how to use additional realm classes included in GlassFish to create realms that authenticate against an LDAP Database, or against a relational database, and how to create realms that "piggyback" into a Solaris server's authentication mechanism.

Finally, we covered how to create custom realm classes for cases where the included ones do not meet our needs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.40.207