Categories: Java, Cryptography
Intro
The Java Authentication and Authorization Service (JAAS) is the standard java framework for verifying who a user is, and what groups/roles they belong to.
JAAS is reasonably simple once the basic concepts are understood, but because it uses a callback-based approach to support pluggability, the data flows are not trivial to understand just by looking at the java APIs. This short article therefore describes how the various parts fit together.
The following information is derived from the book “Java Security (2nd Ed), Scott Oaks” and personal experience. The standard javadocs for the jaas-related classes are also helpful once you know the general concepts.
Principals and Subjects
The interface java.security.Principal represents a name in some namespace. The namespace is represented by the concrete subclass that implements Principal. As examples:
- UserPrincipal represents the name of an operating-system user on the local host machine.
- KerberosPrincipal represents the name of a user account in a kerberos “realm”.
The concrete Principal subclass may also hold some extra properties (such as the Kerberos “realm” that the name is in).
A Principal may also represent the name of a group or role.
However a Principal is by itself an untrustworthy object; anyone can create an instance.
A Subject (as returned by method LoginContext.login) is a set of proven principals, ie contains Principals which one or more LoginModules have (somehow) verified do belong to the current user.
And as a somewhat-related feature, a subject can also have a set of “public credentials” and “private credentials”. An example of a public credential is an RSA “public key”; an example of a private credential is an RSA “private key” or a symmetric key. These keys can sometimes be populated by the login process; in particular, an LDAP login module may retrieve a user’s keys from the LDAP server. Logging in to a web service might cause the http session-id to be stored as a private credential. Perhaps a kerberos login also populates the “private credentials” with a kerberos ticket?
Note that a subject is implicitly data about the current user; by itself it proves that somebody has these credentials but doesn’t say who. For a standalone (single-user) java application the “current user” is obviously the person using the application. In a webserver, storing a subject in an HTTP session is useful for recording that the user with that session has the identities contained in the subject.
LoginContext
This is the class that a java application interacts with to obtain information about the current user; the LoginContext.login methods return a Subject (containing one or more verified Principals).
Optionally, a Subject instance can be passed to the login method in order to store additional principals into an existing Subject.
LoginModules
A login module implements a specific way of authenticating the current user.
The most simple and obvious is a module that accepts a username and password and verifies these against a local file.
One very simple but useful case is for a module to just return the operating-system credentials for the user account that the java code is running as. In this case, no user interaction is required at all - the module simply uses os-specific code to effectively “import” information about the user and their groups into java as a set of Principal objects within a Subject instance.
Other obvious examples include validating username/pwd against a database, or LDAP server.
Login Module Configuration
Exactly which login modules are triggered by a call to LoginContext.login(..) is specified by a named entry within the “configuration” parameter to the LoginContext constructor. If a Configuration object is not provided, then the system-wide default Configuration object is used; this is normally initialised from a configuration file specified via “-Djava.security.auth.login.config=filename”.
A Configuration is a set of named entries; each entry can specify one login module or a stack of modules which get executed in order. A separate parameter to the login method specifies which entry to use.
Each module in a stack adds zero or more Principal objects to the current subject. Each module can be marked as “required”, “sufficient”, “optional” or “requisite”. All modules marked as “required” must approve the user; if any fails then no Principals are added to the Subject. Modules marked as “optional” will add principals to the subject if they pass, and simply do nothing if they fail. The other flags are more subtle - see the official documentation.
People familiar with the linux “pam” authentication system will notice similarities here; pam also supports stacked login modules with required/optional flags.
Login Context Shared State
The LoginContext object that the main java application creates in order to verify a user has internal “shared state” that login modules can cache data in.
This data can be used for communicating between login modules in a stack, eg so that after the first one has asked the user for a password (via a callback-handler), the others can optionally see that password without having to also prompt for it.
Some login modules also cache data which they then use when LoginContext.logout() is invoked. This is a controversial practice - it can be inconvenient for some java applications to keep the original LoginContext instance for each user around until they log out, particularly in webserver environments.
Callbacks and CallbackHandlers
Most login modules need data from the user. As the exact data varies by login-module, the data is not passed in to the LoginContext.login method, but instead each login module calls back into the application passing a list of the data fields it needs.
The “data fields it needs” is represented as a list of Callback objects. This is a very unfortunate choice of name; the class would better have been named “FieldDescriptor” or “InputParameter” or similar. There are several standard classes, including:
- NameCallback : login module wants the user to input a “name”
- PasswordCallback : login module wants the user to input a “password”
- ChoiceCallback : login module wants the user to choose one of a set of values (stored in the provided object)
- ConfirmationCallback : login module wants the user to answer yes/no/cancel to a particular question (question text is in the object)
- TextOutputCallback : login module wants an info/warn/error message shown to the user
The application therefore (normally) needs to create a class implementing CallbackHandler which:
- accepts a list of Callback objects
- throws an UnsupportedCallbackException if it gets an object type it doesn’t support
- otherwise somehow presents the user with the specified messages/questions
- calls a setter method on each of the callbacks with the user-provided data (not for “textoutput”, obviously)
A custom LoginModule can optionally define its own Callback subclasses to ask for unusual data, but that login module would then obviously only be usable with applications coded to expect/support that callback object type.
While theoretically applications should handle all the standard “data fields”, it is quite common for applications to instead only hard-code a user input screen with username/password and possibly “domain/realm”. Such an application therefore can only be used with a subset of the existing login modules.
In the case where the main application knows the login modules will not need any input (eg where it expects that the login module will just retrieve details about the current operating system user the application is running as), then NULL may be provided as the CallbackHandler.
Many of the Callback types have additional methods that return text to be shown to the user, eg a question to be answered or a label for the input field.
Data Flow
The LoginContext.login method invokes the jaas framework which uses the configuration file to determine the set of LoginModules to use. Each login module’s login() method is then invoked in turn. If a login module needs input from the user then it:
- builds a list of Callback objects representing the parameters it needs
- invokes the user-provided callback-handler, which should:
- interact with the user, and
- call the “set” method on each relevant callback passing the user-provided data
If the end of the stack is reached with all “required” modules passing, then the “commit” method is invoked on each login-module. The module then adds a Principal object to the current Subject.
The subject is then returned to the calling code.
Note that the LoginModule.login() method just “verifies” the user without altering the subject; the commit step actually updates the Subject.
Note also that the LoginContext constructor allocates a new instance of each of the LoginModule objects in the selected stack; LoginModule classes therefore do not need to be threadsafe, and can store state about the current login in their own member fields.
Using a Subject
So what use is a Subject?
JAAS does provide a security framework similar to (but separate from) the normal SecurityManager/AccessController system. A policy file with very similar format to a standard java security policy file can “grant rights” to not only code from a specific source, or signed by a specific certificate, but also to specific Principals. This policy is then applied to code that executes the Subject.doAs(Subject, PrivilegedAction) method. As with standard java security, an exception will then be thrown if the code tries to call methods which use normal SecurityManager.checkPermission() checks and some class on the callstack does not have the appropriate right (as granted by the union of the normal security policy and the jaas security policy). This approach doesn’t work well with principals representing individual users, but can be very effectively used to grant rights to principals representing “groups” or “roles”.
Actually, the JAAS policy approach isn’t used much in practice. It is more common for applications to build their own security systems, and simply use Subject.getPrincipals().contains(someRole) to see whether the current user has a particular “role”.
Or in other words, the LoginContext class is about authentication while the “jaas policy” stuff is about authorization, and while JAAS is heavily used for authentication, the authorization part is not nearly so popular.
Securing the Subject class
Principal is an interface, so anybody can create classes implementing the interface.
New principals can be added to an existing subject instance by any code using Subject.getPrincipals().add(..). However it is obviously dangerous to allow untrusted code to directly add principals to an existing subject. Fortunately, the add method requires the caller to have the javax.security.auth.AuthPermisson “modifyPrincipals” right, which applet code (for example) should not.
Note however that Subject is a non-final class, so anybody can create subclasses of it, and getPrincipals() is a non-final method so any code can create their own version of this class which does NOT need the “modifyPrincipals” right. Therefore, only trust subject instances that are returned by the LoginContext class, and never ones that come from untrusted code. And if existing subject instances are accessable to untrusted code then never grant that code the “modifyPrincipals” right
References
- The official javadocs for the LoginContext class