Introduction
This paper explains how to use the Java Authentication and Authorization API (JAAS). It plugs JAAS into the Struts framework. Though this paper focuses on Struts, and in particular the example application distributed with Struts, the lessons learned should be applicable to any MVC web framework.
In addition, while there are many articles on authentication with JAAS [1], [2] , using the API for authorization is relatively undocumented [3]. This paper will show how to use JAAS to secure resources in an MVC architecture.
There are two points of integration with Struts. During the login process and when the client requests a resource from Struts (which will usually be a URL). At each of these points, the application should defer to JAAS classes to perform the action.
This paper will first examine the JAAS infrastructure and then explain how the integration outlined above took place.
Who should read this
This article is for developers who are relatively familiar with web applications and the java security framework. A sample application in Struts is used for the examples [4], but familiarity with Struts is not required. For information on java security, check out Java Security by Scott Oaks. For information on web applications, check out Sun's web site [5]. For information on Struts, check out the Struts web site [6].
Conventions
For the purposes of printing, text that should be on one line will be broken up into more. This will be indicated by a backslash (\) at the end of the line.
Authentication
Login Module
The login module receives information about the user and authenticates the user, thereby verifying that he or she is a valid subject. There are several implementations of login modules currently available. If a custom login module is needed, resources are available [8].
Login Module implementations
Sun provides default implementations for Solaris and NT.
Tagish has several implementations licensed under the GPL [9].
JBoss provides several implementations [10].
Note: For WebLogic 6.1 and Tomcat 3.2, both the jaas.jar and the login module classes needed to be in the non webapp specific classpath.
These login modules are identified by a name in a configuration file and then called by a LoginContext class that JAAS provides. The LoginContext class does not appear to be thread safe--the javadoc states that " a LoginContext should not be used to authenticate more than one Subject. A separate LoginContext should be used to authenticate each different Subject" [11]. Most of these modules expect to be run from an application or on the command line, and thus to be able to interact directly with a user. Since a web application resides on a server, it may be necessary to write an adapter to pull the needed user information from where the web application stores it and place it in a form that the login module can process.
Module Configuration
In addition to putting classes that can do authentication in the correct classpath location, some configuration is required to let the JAAS classes know what type of authentication is possible. JAAS allows complex login schemes [12], however, for most web applications, such complexity is not required. Typically, a username and password will be read from a form, and compared against known server side values. An authentication scheme which verifies a username and password combination against a server side file will be examined below.
Example 1. JAAS login module configuration file
FileLogin
{
com.tagish.auth.FileLogin required debug=true \
pwdFile="/usr/local/tomcat/webapps/struts-example/WEB-INF/passwd";
};
In Example 1, the FileLogin authentication scheme has one required module. FileLogin implemented by the com.tagish.auth.FileLogin class. Both the class name and a token indicating the relationship of the module to the scheme are mandatory. Here the required token indicates that the FileLogin module must validate this login or the scheme as a whole fails. It is also possible to pass additional information to the login module in this configuration file. This module needs to know where its password file is located.
Since this configuration file may be located anywhere on the file system, the authentication classes need to be informed where this file is. There are two ways to do this: adding parameters to a JVM wide general configuration file (java.security), or passing the information in on the command line when starting the JVM.
Setting Login Configuration Information Via java.security
This file is where the JVM looks for security related configuration parameters. It is typically located in the JAVA_HOME/jre/lib/security directory. It is possible to have an arbitrary number of login configuration files detailing any number of authentication schemes. These are "read and unioned into one single configuration" [13].
Example 2. java.security entry for login module configuration
login.config.url.1=file:${java.home}/lib/security/tagish.login
login.config.url.2=file:${java.home}/lib/security/struts.login
This entry specifies the location of files containing authentication scheme definitions. Also, note that java system properties may be referenced in java.security.
If the second line looked like login.config.url.3=file:${java.home}/lib/security/struts.login (note the 3), then struts.login would not be searched for login modules. Scanning begins at login.config.url.1 and continues to increment the suffix until no file is found. This is an implementation detail of the default Sun provided authentication classes. For more information, read about the Login Configuration Provider [14].
Setting Login Configuration Via the Command Line
It is also possible to set a java property on the command line when starting the JVM that tells it where to look for the JAAS login scheme configuration file. The property name is java.security.auth.login.config.
Example 3. Setting the location of the login scheme configuration file on the command line
$ java ... \
-Djava.security.auth.login.config==$JAVA_HOME/jre/lib/security/tagish.login ...
This command line overrides any previously set value for the login scheme configuration file location. In typical java fashion, if there was just one = in Example 3, an additional location for the login scheme configuration file would be specified.
Integrating Authentication Into Struts
After the login module is recognized by JAAS, hooks into the web application need to be written. In the example application, an Action class takes a username and password from its Form class and authenticates the user against a database. This is the logical place to hook in the JAAS authentication module.
The Struts example application has a LogonAction class. Initially, the relevant portion of this class looks like this (note that this code is unchanged except to break up lines for ease of printing).
Example 4. Initial LogonAction Authentication
125 String username = ((LogonForm) form).getUsername();
126 String password = ((LogonForm) form).getPassword();
127 Hashtable database = (Hashtable)
128 servlet.getServletContext().getAttribute( \
Constants.DATABASE_KEY \
);
129 if (database == null)
130 errors.add(ActionErrors.GLOBAL_ERROR,
131 new ActionError("error.database.missing"));
132 else {
133 user = (User) database.get(username);
134 if ((user != null) && !user.getPassword().equals(password))
135 user = null;
136 if (user == null)
137 errors.add(ActionErrors.GLOBAL_ERROR,
138 new ActionError("error.password.mismatch"));
139 }
On line 125 and 126, the username and password that were submitted on the form are extracted into local variables. The code looks for an authentication database and verifies its existence on lines 127-132. Line 134 is where the actual authentication occurs. If the user exists in the database and the password in the database is the same as the password entered on the form, then the user is authenticated.
This approach works just fine for a sample application. The LogonAction class does all of the authentication in the perform method. But the LogonAction class should be a thin layer deferring the actual work to business objects. In addition, switching the source users are authenticated against requires changes to this class since it is hard coded to expect a password and username. Also, as mentioned in the "Login Module" section, using the JAAS authentication module will be difficult, as most implementations expect some level of user interaction. Putting a business object between the Struts application classes and the JAAS classes can make life easier.
Example 5. Modified LogonAction Authentication
125 String username = ((LogonForm) form).getUsername();
126 String password = ((LogonForm) form).getPassword();
127 Hashtable database = (Hashtable)
128 servlet.getServletContext().getAttribute(Constants.DATABASE_KEY)
129 if (database == null)
130 errors.add(ActionErrors.GLOBAL_ERROR,
131 new ActionError("error.database.missing"));
132 else {
133 Auth fa = new com.xor.auth.FileAuth(username,password);
134 if (fa.authenticate()) {
135 user = (User) database.get(username);
136 HttpSession sess = request.getSession();
137 sess.setAttribute(Auth.SUBJECT_SESSION_KEY, fa.getSubject());
138 }
Lines 125-132 are the same in this example as they were in Example 4. However, rather than the LogonAction class performing the authentication, it defers to a special Auth class. Note that on line 135, the database is still used to get the User object, since the sample application used it in other code.
Auth is a custom interface specifying the contract that the adapter for the JAAS classes must fulfill to interface with web applications in general and this example application in particular. FileAuth is an implementation of that interface which adapts the com.tagish.auth.FileLogin login module specified in Example 1.
Calling the Tagish Authentication Module
The FileAuth class defers to the FileLogin class for almost all its functionality.
Example 6. The authenticate Method of the FileAuth Class
1 public boolean authenticate() {
2 try {
3 lc = new LoginContext("FileLogin", \
new MyCallBackHandler(username,password));
4 lc.login();
5 } catch (LoginException le) {
6 return false;
7 }
8 return true;
9 }
On line 3, the LoginContext object is told to look for the FileLogin authentication scheme, and passed a custom callback handler. Normally, callback handlers interact with the user and retrieve information needed for authentication, but the ActionForm class has already done this. In a way, the form is a "call forward" handler, because the request/response paradigm of the web does not fit well with the client/server architecture of which the callback handler is part. The problem is worked around by passing the needed information to the callback handler on instantiation.
On line 4, the LoginContext attempts to login the user via the modules found in the FileLogin authentication scheme. If the login fails for any reason, a LoginException will be thrown, otherwise flow continues normally. A Subject is instantiated by the login module and exposed as a property of the LoginContext object. The Subject is also populated with the Principals associated with that user by the login module.
Cache Subject in Session
After successfully authenticating the user, the Subject should be stored in the HttpSession. (The Subject is small; one Subject with one Principal serialized to a file 337 bytes in size.) This caching allows other parts of the application to perform authorization checks without requiring the user to login again. It also ensures that the other parts of the application are aware that the user has been authenticated, and logs out the user when the HttpSession expires. The storage process is shown on line 137 of Example 5; here the portion of the API of the login module that returns the newly authenticated Subject is examined. To retrieve the subject, the FileAuth class again defers to the LoginContext.
Example 7. The FileAuth Class Exposes the Subject
1 public Subject getSubject() {
2 if (lc == null) {
3 throw new IllegalStateException("either login failed or \
the authenticate method hasn't been called.");
5 } else {
6 return lc.getSubject();
7 }
8 }
The getSubject method can only be called after the authenticate method.
Of course, another option would be to store the username and password in the session, and re-authenticate every time. If the Subject were large and the authentication process was fairly fast, this option might be viable.
Authorization
Permissions
Permissions are the heart of authorization; they control access to resources. However, the JAAS permissions are built on top of the existing java security model. This model is very good for controlling access to resources like sockets and files, but has no concept of URLs. Thus, to apply JAAS to a web application, a new permission class must be created.
Create permission class
A custom Permission class that understands URLs must be created. There are two ways to do this.
Extending java.security.BasicPermission is one option. Using this would tie permissions to literal URLs (e.g, one could say that the admin principal should have access to the /admin/index.do page). However, the other option would be better: to create a URLPermission class extended the java.security.Permission class and handled wild cards in a manner similar to the java.io.FilePermission class [15]. Then, one could say that the admin principal should have access to the /admin/ directory and all resources below it.
In either case, since URLs are read only, there is no need for any of these permissions to have an action attribute. On the other hand, it may be useful to specify an attribute of a Permission to determine whether a given URL may only be viewed over a secure connection. For more on extending Permissions in novel manners, see the interesting IBM article titled Extend JAAS for class instance-level authorization [16].
However, since this is a proof of concept, a BasicPermission implementation is fine. For a production system, subclassing Permission would be required.
Access Control Policy Configuration Files
The default configuration for JAAS permissions looks much like the configuration for normal java security. Java security entries consist of a listing of permissions. The permissions can be limited to either a specific code base, or code that has been signed by a specific person, or both. JAAS permission listings can have none, one or both of these elements. However, at least one Principal association is required.
Example 8. Sample JAAS policy file
grant Principal * * {
permission com.xor.auth.perm.URLPermission \
"/struts-example/logoff.do";
};
grant Principal com.tagish.auth.TypedPrincipal "user" {
permission com.xor.auth.perm.URLPermission \
"/struts-example/editRegistration.do";
};
grant Principal com.tagish.auth.TypedPrincipal "admin" {
permission com.xor.auth.perm.URLPermission \
"/struts-example/editRegistration.do";
permission com.xor.auth.perm.URLPermission \
"/struts-example/adminMenu.do";
};
The first grant allows everyone to view the logoff.do resource. Anyone with the user Principal is allowed to view /struts-example/editRegistration.do.
At times there will be principals that are granted a superset of the permissions granted to other principals. For example, in Example 8, subjects with the admin principal can view all resources that subject with the user principal can. However, the /struts-example/editRegistration.do URL is listed twice (once in the user section and once in the admin section). It would be nice to be able to avoid duplicating permissions and delineating this relationship between principals, but this is not possible. While more than one principal can be listed for a given set of permissions, if that happens, "[t]he current Subject running the code must have all of the specified Principals in its Principal set to be granted the entry's Permissions" [17]. However, this problem may be dealt with by granting all users with the admin Principal the user Principal as well.
On the other hand, it is possible to have permissions be supersets of other permissions. The implies method of the Permission class can be overridden to provide this behavior. For example, there could be an admin Permission which could imply all other URL permissions.
Note: Example 8 is an example of a file based implementation of the policy. It canbe replaced with a RDBMS implementation by changing the value of the auth.policy.provider variable in the java.security file.
Since the policy file may be located anywhere, JAAS needs to be told where to look for it (this problem is similar to that faced by the authentication configuration setup detailed in Section 2.2. There are two ways to do this: adding parameters to a system wide general configuration file (java.security), or passing the information in as a command line option when starting the JVM.
Note: With WebLogic 6.1, only the command line method worked. Tomcat 3.2 seemed to work with both methods.
Setting the JAAS Policy File Location in the java.security file
This file is where the JVM looks for security related configuration parameters. It is typically located in the JAVA_HOME/jre/lib/security directory. It is possible to have arbitrary numbers of JAAS policy files. These are "read and unioned into one single policy" [18].
Example 9. java.security entry for permission policy configuration
auth.policy.url.1=file:${java.home}/lib/security/struts.policy
auth.policy.url.2=file:${user.home}/.struts.policy
This example should check the policy files in both places. Note also that java system properties can be referenced in the java.security file.
If the second line looked like auth.policy.url.3=file:${user.home}/.struts.policy (note the 3), then the .struts.policy file would not be searched for permission sets. Scanning starts at auth.policy.url.1 and increments the suffix by one until there is no file found. This is an implementation detail of the default Sun permission file parsing class. Obviously, an authorization scheme that used a database would have different behavior.
Setting the JAAS Policy File Location Via the Command Line
It is also possible to set a java property on the command line when starting the JVM that tells it where to look for the JAAS policy file. The property name is java.security.auth.policy.
Example 10. Setting the location of the JAAS policy file on the command line
$ java ... \
-Djava.security.auth.policy==$JAVA_HOME/jre/lib/security/struts.policy ...
This command line overrides any previously set location of the JAAS policy file. If there was only one =, the property would indicate an additional policy file path.
Integrating Authorization into Struts
Now that permissions have been set up and JAAS knows about them, it is time to actually apply them in the context of the application. Every java class that accesses a potentially security sensitive resource needs to check to see if the SecurityManager is running, and if so verify that the calling class is running in a context that allows the access.
Example 11. Typical permission check
Permission p = new FilePermission("/tmp/foo","read");
SecurityManager s = System.getSecurityManager();
if ( s != null) s.checkPermission(p);
The code here is trying to read /tmp/foo. If no SecurityManager is installed (typically via the -Djava.security.manager command line switch), System.getSecurityManager() returns null, and access does not need to be verified.
The web application needs to perform a similar check for each URL that the user is trying to view. In an MVC architecture, the logical place to put such access control is the controller. In Struts, the ActionServlet is the controller. For the sample application the controller class, org.apache.struts.action.ActionServlet, was subclassed, and the process method was overridden.
Installing Access Control Logic into the ActionServlet
First of all, it is important to note that when the ActionServlet is subclassed to provide authorization services, only resources that the ActionServlet controls are protected. Thus, in the example application, only Actions are protected. In a real application, it'd be better to have two methods of access control:
A central servlet which checks authorization before any URL is viewed. For example, all users may need to be logged in before viewing any pages. (If using the 1.3 version of the servlet specification, a filter might be a good choice.)
A JSP tag or other method which may be used to check to see if a user has a given principal before showing content fragments. For example, a menu item may only be accessible to users with the admin permission.
Both of these should delegate to a business class that does the actual security check.
There are at least two pages whose view should not be protected at all: the login page and the login error page. In addition, there are certain security attributes that are peculiar to web applications; for example, some users may need to have all interaction over an SSL connection. The controller needs to do at least three things to provide authorization services for a web application.
Recognize which pages are viewable by every authenticated user and respond accordingly.
Recognize which state, secure or non secure, a request is in and respond accordingly.
Check to see that a user is allowed to view a given URL and respond accordingly.
The ActionServlet Subclass
Example 12. The Overridden process Method
1 protected void process(HttpServletRequest request, \
HttpServletResponse response)
2 throws ServletException, java.io.IOException
3 {
4 String loginPage = request.getContextPath()+"/logon.do";
5 String pageReq = request.getRequestURI();
6 Permission perm = \
PermissionFactory.getInstance().getPermission(pageReq);
7 Subject subject = \
((Subject)(request.getSession().getAttribute( \
Auth.SUBJECT_SESSION_KEY)));
8 if (subject == null && \
(! request.getRequestURI().equals(loginPage))) {
9 // redirect to login page
10 } else if (subject == null && \
request.getRequestURI().equals(loginPage)) {
11 // login page is always permitted
12 super.process(request,response);
13
14 } else {
15 if ( ! AuthUtils.permitted(subject, perm) ) {
16 // subject is not permitted; redirect to error page
17 } else {
18 super.process(request,response);
19 }
20 }
21 }
Lines 6 and 7 get needed information including the Permission that represents the URL the user is trying to access (PermissionFactory is a factory class that returns the appropriate Permission), and the cached Subject.
On lines 8-14, the case of the unauthenticated user is handled by redirecting the user to the login page, where access is always allowed (line 12). The reason that this can't be handled by the wildcard grant in Example 8 is that the Subject is null in this case. If the program catches this and creates a new Subject, that Subject still has no Principals and thus is still denied access. For a production system, it's conceivable this case could be handled with an anonymous Principal.
If this is a request for any other resource than the login page, on line 15 the access control class is called. Access is either disallowed, or the process method of the superclass is called.
The ActionServlet should not know about the particulars of the access control; this should instead be handled by a business class. For this example, such a business class was written; it is examined in the next section.
The Access Control Class
This class is similar to the java.security.AccessController class [19], and ends defers to that class eventually (via the SecurityManager). The logic in the class can then be reused anywhere that authorization is needed. The class will basically check whether a Subject has a given Permission.
Example 13. An Abridged Permission Checking Class
1 package com.xor.auth;
2
3 import java.security.*;
4 import javax.security.auth.*;
5 import java.util.*;
6
7 public class AuthUtils {
8
9 static public boolean permitted(Subject subj, final Permission p) {
10 final SecurityManager sm;
11 if (System.getSecurityManager() == null) {
12 sm = new SecurityManager();
13 } else {
14 sm = System.getSecurityManager();
15 }
16 try {
17 Subject.doAsPrivileged(subj, new PrivilegedExceptionAction() {
18 public Object run() {
19 sm.checkPermission(p);
20 return null;
21 }
22 },null);
23 return true;
24 } catch (AccessControlException ace) {
25 return false;
26 } catch (PrivilegedActionException pae) {
27 return false;
28 }
29 }
30 }
The example has had all logging, error checking and comments removed for brevity. Production code should, for example, test to see that neither the Permission nor the Subject are null.
Lines 10-15 create a SecurityManager. If the application happens to be one of the rare java applications that run with a SecurityManager installed, then that SecurityManager should be used. If not, a new one is created to check the permissions on line 12.
Lines 16-23 do the actual permission check. If the Subject does not have a Principal which has been granted the Permission, the SecurityManager will throw an exception. For reasons of cleanliness and clarity, this exception is caught and converted to false, which is returned to the calling class. Otherwise, the action is allowed, and true is returned.
The null on line 22 is very important; it tells the SecurityManager to consider this resource access in an isolated context, ignoring the permissions of code currently on the execution stack. For further information, see chapter 5 of Java Security.
Note: If the application is running with a SecurityManager enabled, make certain that the AuthUtils class has been granted the doAsPrivileged AuthPermission in the standard java security policy file. Otherwise, this permission checker will not be able to run; a SecurityException will be thrown when line 17 is reached.
Conclusion
The authentication piece of JAAS seems fairly bulletproof. The idea of pluggable authentication modules is great and the developer can leverage a number of existing modules to ease development.
Using JAAS to leverage the SecurityManager for authorization is entirely commensurate with the java security model. There are resources that only certain users with certain principals should be able to see. Rather than reinvent an access control layer, it makes sense to use the one that java already provides.
However, there are some caveats. This was an extremely simplistic example, and the reader will have noted the number of places where parts of the system need to be replaced to create a production system; these include a new controller, permission class, and policy implementation. In addition, this permission model does not map well to the concept of different protocols used to view a URL.
Definitions
user: a real world entity. It can be another computer system or a human being. This is not represented by an object.
subject: the user as seen by the web application. Subject is the class that represents this concept.
principal: one view of the subject. The API states that it "represents the abstract notion of a principal, which can be used to represent any entity, such as an individual, a corporation, and a login id A Principal is a class that represents a principal" [7]. A principal can be thought of as a role or a group, but those terms have special meaning in J2EE (in the servlet container, for one).
resource: anything in a system to which unlimited access is not granted to all.
permission: access to a resource. The Permission class represents a triplet of resource, action and name. Permissions can be granted to a Principal.
Authentication: the act of verifying that a user is a subject and granting the user certain principals; "who you are."
Authorization: the act of verifying that a user is allowed to access a certain resource; "what you may do."
Authentication module: one method of authenticating a user. Examples include verifying against /etc/passwd and examining the contents of a cookie.
Authentication scheme: a combination of authentication modules.
posted on 2005-08-17 17:38
my java 阅读(1428)
评论(0) 编辑 收藏 所属分类:
java身份认证转帖