JAAS Authentication Tutorial
This tutorial describes how to use JAAS for the authentication of users: to reliably and securely determine who is currently running.
JAAS authentication is performed in a pluggable fashion. This permits Java applications to remain independent from underlying authentication technologies. New or updated technologies can be plugged in without requiring modifications to the application itself. An implementation for a particular authentication technology to be used is determined at runtime. The implementation is specified in a login configuration file. The authentication technology used for this tutorial is very basic, just ensuring that the user specifies a particular name and password.
The rest of this tutorial consists of the following sections:
- The Authentication Tutorial Code
- The Login Configuration
- Running the JAAS Authentication Tutorial Code
If you want to first see the tutorial code in action, you can skip directly to Running the JAAS Authentication Tutorial Code and then go back to the other sections to learn about coding and configuration file details.
The Authentication Tutorial Code
The code for this tutorial consists of three files:
SampleAcn.java
contains the sample application class (SampleAcn
) and another class used to handle user input (MyCallbackHandler
). The code in this file is the only code you need to understand for this tutorial. Your application will only indirectly use the other source files.SampleLoginModule.java
is the class specified by the tutorial's login configuration file,sample_jaas.config
, described in The Login Configuration File for the JAAS Authentication Tutorial as the class implementing the desired underlying authentication.SampleLoginModule
's user authentication consists of simply verifying that the name and password specified by the user have specific values.SamplePrincipal.java
is a sample class implementing the java.security.Principal interface. It is used bySampleLoginModule
.
SampleAcn.java
Our authentication tutorial application code is contained in a single source file, SampleAcn.java
. That file contains two classes:
The SampleAcn Class
The main
method of the SampleAcn
class performs the authentication and then reports whether or not authentication succeeded.
The code for authenticating the user is very simple, consisting of just two steps:
Note:
TheSampleAcn
class also demonstrates the following:
- How to run code with a specified Subject with the Subject.callAs(Subject, Callable<T>) method
- How to access the parent thread's current Subject from a child thread using structured concurrency.
See The callAs and current Methods for Performing an Action as a Particular Subject for more information.
First the basic code is shown, followed by The Complete SampleAcn Class Code, complete with the import statement it requires and error handling.
Instantiating a LoginContext
In order to authenticate a user, you first need a javax.security.auth.login.LoginContext
. Here is the basic way to instantiate a LoginContext:
import javax.security.auth.login.*;
// ...
LoginContext lc =
new LoginContext(<config file entry name>,
<CallbackHandler to be used for user interaction>);
and here is the specific way our tutorial code does the instantiation:
import javax.security.auth.login.*;
// ...
LoginContext lc =
new LoginContext("Sample",
new MyCallbackHandler());
The arguments are the following:
- The name of an entry in the JAAS login configuration file
This is the name for the LoginContext to use to look up an entry for this application in the JAAS login configuration file, described in The Login Configuration. Such an entry specifies the class(es) that implement the desired underlying authentication technology(ies). The class(es) must implement the LoginModule interface, which is in the
javax.security.auth.spi
package.In our sample code, we use the
SampleLoginModule
supplied with this tutorial. TheSampleLoginModule
performs authentication by ensuring that the user types a particular name and password.The entry in the login configuration file we use for this tutorial,
sample_jaas.config
(see The Login Configuration File for the JAAS Authentication Tutorial), has the name "Sample", so that is the name we specify as the first argument to the LoginContext constructor. - A CallbackHandler instance
When a LoginModule needs to communicate with the user, for example to ask for a user name and password, it does not do so directly. That is because there are various ways of communicating with a user, and it is desirable for LoginModules to remain independent of the different types of user interaction. Rather, the LoginModule invokes a javax.security.auth.callback.CallbackHandler to perform the user interaction and obtain the requested information, such as the user name and password.
An instance of the particular CallbackHandler to be used is specified as the second argument to the LoginContext constructor. The LoginContext forwards that instance to the underlying LoginModule (in our case
SampleLoginModule
). An application typically provides its own CallbackHandler implementation. A simple CallbackHandler, TextCallbackHandler, is provided in thecom.sun.security.auth.callback
package to output information to and read input from the command line. However, we instead demonstrate the more typical case of an application providing its own CallbackHandler implementation, described in The MyCallbackHandler Class.
Calling the LoginContext's login Method
Once we have a LoginContext lc
, we can call its login
method to carry out the authentication process:
lc.login();
The LoginContext instantiates a new empty javax.security.auth.Subject object (which represents the user or service being authenticated; see Subject). The LoginContext constructs the configured LoginModule (in our case SampleLoginModule
) and initializes it with this new Subject and MyCallbackHandler
.
The LoginContext's login
method then calls methods in the SampleLoginModule
to perform the login and authentication. The SampleLoginModule
will utilize the MyCallbackHandler
to obtain the user name and password. Then the SampleLoginModule
will check that the name and password are the ones it expects.
If authentication is successful, the SampleLoginModule
populates the Subject with a Principal representing the user. The Principal the SampleLoginModule
places in the Subject is an instance of SamplePrincipal
, which is a sample class implementing the java.security.Principal interface.
The calling application can subsequently retrieve the authenticated Subject by calling the LoginContext's getSubject
method, although doing so is not necessary for this tutorial.
The Complete SampleAcn Class Code
Now that you have seen the basic code required to authenticate the user, we can put
it all together into the full class in SampleAcn.java
, which includes
relevant import statements and error handling:
SampleAcn.java
package sample;
import java.io.*;
import java.util.*;
import javax.security.auth.login.*;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import java.security.Principal;
import java.util.concurrent.Callable;
import java.util.concurrent.StructuredTaskScope;
/**
* <p>This sample application attempts to authenticate a user and reports whether
* or not the authentication was successful.</p>
*
* <p>It also demonstrates how to run code with a specified Subject with the
* Subject.callAs(Subject, Callable<T? method and how to access the parent
* thread's current Subject from a child thread using structured concurrency and
* the Subject.current() method.</p>
*/
public class SampleAcn {
/**
* <p>Attempt to authenticate the user.</p>
*
* @param args input arguments for this application. These are ignored.
*/
public static void main(String[] args) {
// Obtain a LoginContext, needed for authentication. Tell it
// to use the LoginModule implementation specified by the
// entry named "Sample" in the JAAS login configuration
// file and to also use the specified CallbackHandler.
LoginContext lc = null;
try {
lc = new LoginContext("Sample", new MyCallbackHandler());
} catch (LoginException le) {
System.err.println("Cannot create LoginContext. "
+ le.getMessage());
System.exit(-1);
}
// The user has 3 attempts to authenticate successfully
int i;
for (i = 0; i < 3; i++) {
try {
// Attempt authentication
lc.login();
// If we return with no exception, authentication succeeded
break;
} catch (LoginException le) {
System.err.println("Authentication failed:");
System.err.println(" " + le.getMessage());
try {
Thread.currentThread().sleep(3000);
} catch (Exception e) {
// ignore
}
}
}
// did they fail three times?
if (i == 3) {
System.out.println("Sorry");
System.exit(-1);
}
System.out.println("Authentication succeeded!");
Subject mySubject = lc.getSubject();
// Let's see what Principals we have
Iterator principalIterator = mySubject.getPrincipals().iterator();
System.out.println("Authenticated user has the following Principals:");
while (principalIterator.hasNext()) {
Principal p = (Principal)principalIterator.next();
System.out.println("\t" + p.toString());
}
System.out.println("User has " +
mySubject.getPublicCredentials().size() +
" Public Credential(s)");
Callable<Void> anotherAction = () -> {
// Retrieve the current subject
Subject s = Subject.current();
System.out.println("\nCurrent subject: " + s);
// Add a new Principal to the current subject
Random r = new Random();
String pn = Integer.toString(r.nextInt());
Principal p = new sample.principal.SamplePrincipal(pn);
System.out.println("\nAdding principal " + pn);
s.getPrincipals().add(p);
// List the current subject's Principals
System.out.println("\nAuthenticated user has the following Principals:");
Iterator pi = s.getPrincipals().iterator();
while (pi.hasNext()) {
Principal nextp = (Principal)pi.next();
System.out.println("\t" + nextp.toString());
}
return null;
};
// Run the Callable anotherAction with the current subject
Subject.callAs(mySubject, anotherAction);
// The following demonstrates how to retrieve the parent thread's
// current subject from a child thread with structured concurrency.
Callable<Void> addRandomPrincipal = () -> {
Subject s = Subject.current();
// Add a new Principal
Random r = new Random();
String pn = Integer.toString(r.nextInt());
Principal p = new sample.principal.SamplePrincipal(pn);
System.out.println("\nAdding principal " + pn);
s.getPrincipals().add(p);
return null;
};
Callable<Void> structuredAction = () -> {
try (var scope = new StructuredTaskScope<>()) {
scope.fork(addRandomPrincipal);
scope.fork(addRandomPrincipal);
scope.fork(addRandomPrincipal);
scope.join();
Subject s = Subject.current();
System.out.println("\nCurrent subject: " + s);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
};
// Run the Callable anotherAction with the current subject
Subject.callAs(mySubject, structuredAction);
}
}
/**
* <p>The application implements the CallbackHandler.</p>
*
* <p>This application is text-based. Therefore, it displays information
* to the user using the OutputStreams System.out and System.err,
* and gathers input from the user using the InputStream System.in.</p>
*/
class MyCallbackHandler implements CallbackHandler {
/**
* <p>Invoke an array of Callbacks.</p>
*
* @param callbacks an array of <code>Callback</code> objects which contain
* the information requested by an underlying security
* service to be retrieved or displayed.
*
* @exception java.io.IOException if an input or output error occurs. <p>
*
* @exception UnsupportedCallbackException if the implementation of this
* method does not support one or more of the Callbacks
* specified in the <code>callbacks</code> parameter.
*/
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof TextOutputCallback) {
// display the message according to the specified type
TextOutputCallback toc = (TextOutputCallback)callbacks[i];
switch (toc.getMessageType()) {
case TextOutputCallback.INFORMATION:
System.out.println(toc.getMessage());
break;
case TextOutputCallback.ERROR:
System.out.println("ERROR: " + toc.getMessage());
break;
case TextOutputCallback.WARNING:
System.out.println("WARNING: " + toc.getMessage());
break;
default:
throw new IOException("Unsupported message type: " +
toc.getMessageType());
}
} else if (callbacks[i] instanceof NameCallback) {
// prompt the user for a username
NameCallback nc = (NameCallback)callbacks[i];
System.err.print(nc.getPrompt());
System.err.flush();
nc.setName((new BufferedReader
(new InputStreamReader(System.in))).readLine());
} else if (callbacks[i] instanceof PasswordCallback) {
// prompt the user for sensitive information
PasswordCallback pc = (PasswordCallback)callbacks[i];
System.err.print(pc.getPrompt());
System.err.flush();
pc.setPassword(readPassword(System.in));
} else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
// Reads user password from given input stream.
private char[] readPassword(InputStream in) throws IOException {
char[] lineBuffer;
char[] buf;
int i;
buf = lineBuffer = new char[128];
int room = buf.length;
int offset = 0;
int c;
loop: while (true) {
switch (c = in.read()) {
case -1:
case '\n':
break loop;
case '\r':
int c2 = in.read();
if ((c2 != '\n') && (c2 != -1)) {
if (!(in instanceof PushbackInputStream)) {
in = new PushbackInputStream(in);
}
((PushbackInputStream)in).unread(c2);
} else
break loop;
default:
if (--room < 0) {
buf = new char[offset + 128];
room = buf.length - offset - 1;
System.arraycopy(lineBuffer, 0, buf, 0, offset);
Arrays.fill(lineBuffer, ' ');
lineBuffer = buf;
}
buf[offset++] = (char) c;
break;
}
}
if (offset == 0) {
return null;
}
char[] ret = new char[offset];
System.arraycopy(buf, 0, ret, 0, offset);
Arrays.fill(buf, ' ');
return ret;
}
}
The MyCallbackHandler Class
In some cases a LoginModule must communicate with the user to obtain authentication information. LoginModules use a javax.security.auth.callback.CallbackHandler
for this purpose. An application can either use one of the sample implementations provided in the com.sun.security.auth.callback
package or, more typically, write a CallbackHandler implementation. The application passes the CallbackHandler as an argument to the LoginContext instantiation. The LoginContext forwards the CallbackHandler directly to the underlying LoginModules.
The tutorial sample code supplies its own CallbackHandler implementation, the MyCallbackHandler
class in .
CallbackHandler is an interface with one method to implement:
void handle(Callback[] callbacks)
throws java.io.IOException, UnsupportedCallbackException;
The LoginModule passes the CallbackHandler handle method an array of appropriate javax.security.auth.callback.Callbacks, for example a NameCallback for the user name and a PasswordCallback for the password, and the CallbackHandler performs the requested user interaction and sets appropriate values in the Callbacks.
The MyCallbackHandler
handle
method is structured as follows:
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof TextOutputCallback) {
// display a message according to a specified type
. . .
} else if (callbacks[i] instanceof NameCallback) {
// prompt the user for a username
. . .
} else if (callbacks[i] instanceof PasswordCallback) {
// prompt the user for a password
. . .
} else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
A CallbackHandler handle method is passed an array of Callback instances, each of a particular type (NameCallback, PasswordCallback, etc.). It must handle each Callback, performing user interaction in a way that is appropriate for the executing application.
MyCallbackHandler
handles three types of Callbacks: NameCallback to prompt the user for a user name, PasswordCallback to prompt for a password, and TextOutputCallback to report any error, warning, or other messages the SampleLoginModule wishes to send to the user.
The handle method handles a TextOutputCallback by extracting the message to be reported and then printing it to System.out
, optionally preceded by additional wording that depends on the message type. The message to be reported is determined by calling the TextOutputCallback's getMessage method and the type by calling its getMessageType method. Here is the code for handling a TextOutputCallback:
if (callbacks[i] instanceof TextOutputCallback) {
// display the message according to the specified type
TextOutputCallback toc = (TextOutputCallback)callbacks[i];
switch (toc.getMessageType()) {
case TextOutputCallback.INFORMATION:
System.out.println(toc.getMessage());
break;
case TextOutputCallback.ERROR:
System.out.println("ERROR: " + toc.getMessage());
break;
case TextOutputCallback.WARNING:
System.out.println("WARNING: " + toc.getMessage());
break;
default:
throw new IOException("Unsupported message type: " +
toc.getMessageType());
}
The handle
method handles a NameCallback by prompting the user for a user name. It does this by printing the prompt to System.err
. It then sets the name for use by the SampleLoginModule
by calling the NameCallback's setName
method, passing it the name typed by the user:
} else if (callbacks[i] instanceof NameCallback) {
// prompt the user for a username
NameCallback nc = (NameCallback)callbacks[i];
System.err.print(nc.getPrompt());
System.err.flush();
nc.setName((new BufferedReader
(new InputStreamReader(System.in))).readLine());
Similarly, the handle
method handles a PasswordCallback by printing a prompt to System.err
to prompt the user for a password. It then sets the password for use by the SampleLoginModule
by calling the PasswordCallback's setPassword
method, passing it the password typed by the user:
} else if (callbacks[i] instanceof PasswordCallback) {
// prompt the user for sensitive information
PasswordCallback pc = (PasswordCallback)callbacks[i];
System.err.print(pc.getPrompt());
System.err.flush();
pc.setPassword(System.console().readPassword());
SampleLoginModule.java and SamplePrincipal.java
SampleLoginModule.java
implements the LoginModule
interface. SampleLoginModule
is the class specified by the tutorial's login configuration file (see The Login Configuration File for the JAAS Authentication Tutorial) as the class implementing the desired underlying authentication. SampleLoginModule
's user authentication consists of simply verifying that the name and password specified by the user have specific values. This SampleLoginModule
is specified by the tutorial's login configuration file as the LoginModule to use because (1) It performs a basic type of authentication suitable for any environment and thus is appropriate for a tutorial for all users, and (2) It provides an example LoginModule implementation for experienced programmers who require the ability to write a LoginModule implementing an authentication technology.
SamplePrincipal.java
is a sample class implementing the java.security.Principal interface. If authentication is successful, the SampleLoginModule
populates a Subject with a SamplePrincipal
representing the user.
Important: If you are an application writer, you do not need to know how to write a LoginModule or a Principal implementation. You do not need to examine the SampleLoginModule
or SamplePrincipal
code. All you have to know is how to write your application and specify configuration information (such as in a login configuration file) such that the application will be able to utilize the LoginModule specified by the configuration to authenticate the user. You need to determine which LoginModule(s) you want to use and read the LoginModule's documentation to learn about what options you can specify values for (in the configuration) to control the LoginModule's behavior.
Any vendor can provide a LoginModule implementation that you can use. Some implementations are supplied with the JDK from Oracle, as listed in Appendix B: JAAS Login Configuration File.
Information for programmers who want to write a LoginModule can be found in Java Authentication and Authorization Service (JAAS): LoginModule Developer's Guide.
SampleLoginModule.java
package sample.module;
import java.util.*;
import java.io.IOException;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import sample.principal.SamplePrincipal;
/**
* <p> This sample LoginModule authenticates users with a password.
*
* <p> This LoginModule only recognizes one user: testUser
* <p> testUser's password is: testPassword
*
* <p> If testUser successfully authenticates itself,
* a <code>SamplePrincipal</code> with the testUser's user name
* is added to the Subject.
*
* <p> This LoginModule recognizes the debug option.
* If set to true in the login Configuration,
* debug messages will be output to the output stream, System.out.
*
*/
public class SampleLoginModule implements LoginModule {
// initial state
private Subject subject;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
// configurable option
private boolean debug = false;
// the authentication status
private boolean succeeded = false;
private boolean commitSucceeded = false;
// username and password
private String username;
private char[] password;
// testUser's SamplePrincipal
private SamplePrincipal userPrincipal;
/**
* Initialize this <code>LoginModule</code>.
*
* @param subject the <code>Subject</code> to be authenticated. <p>
*
* @param callbackHandler a <code>CallbackHandler</code> for communicating
* with the end user (prompting for user names and
* passwords, for example). <p>
*
* @param sharedState shared <code>LoginModule</code> state. <p>
*
* @param options options specified in the login
* <code>Configuration</code> for this particular
* <code>LoginModule</code>.
*/
public void initialize(Subject subject,
CallbackHandler callbackHandler,
Map<java.lang.String, ?> sharedState,
Map<java.lang.String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
// initialize any configured options
debug = "true".equalsIgnoreCase((String)options.get("debug"));
}
/**
* Authenticate the user by prompting for a user name and password.
*
* @return true in all cases since this <code>LoginModule</code>
* should not be ignored.
*
* @exception FailedLoginException if the authentication fails. <p>
*
* @exception LoginException if this <code>LoginModule</code>
* is unable to perform the authentication.
*/
public boolean login() throws LoginException {
// prompt for a user name and password
if (callbackHandler == null)
throw new LoginException("Error: no CallbackHandler available " +
"to garner authentication information from the user");
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("user name: ");
callbacks[1] = new PasswordCallback("password: ", false);
try {
callbackHandler.handle(callbacks);
username = ((NameCallback)callbacks[0]).getName();
char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
if (tmpPassword == null) {
// treat a NULL password as an empty password
tmpPassword = new char[0];
}
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0,
password, 0, tmpPassword.length);
((PasswordCallback)callbacks[1]).clearPassword();
} catch (java.io.IOException ioe) {
throw new LoginException(ioe.toString());
} catch (UnsupportedCallbackException uce) {
throw new LoginException("Error: " + uce.getCallback().toString() +
" not available to garner authentication information " +
"from the user");
}
// print debugging information
if (debug) {
System.out.println("\t\t[SampleLoginModule] " +
"user entered user name: " +
username);
System.out.print("\t\t[SampleLoginModule] " +
"user entered password: ");
for (int i = 0; i < password.length; i++)
System.out.print(password[i]);
System.out.println();
}
// verify the username/password
boolean usernameCorrect = false;
boolean passwordCorrect = false;
if (username.equals("testUser"))
usernameCorrect = true;
if (usernameCorrect &&
password.length == 12 &&
password[0] == 't' &&
password[1] == 'e' &&
password[2] == 's' &&
password[3] == 't' &&
password[4] == 'P' &&
password[5] == 'a' &&
password[6] == 's' &&
password[7] == 's' &&
password[8] == 'w' &&
password[9] == 'o' &&
password[10] == 'r' &&
password[11] == 'd') {
// authentication succeeded!!!
passwordCorrect = true;
if (debug)
System.out.println("\t\t[SampleLoginModule] " +
"authentication succeeded");
succeeded = true;
return true;
} else {
// authentication failed -- clean out state
if (debug)
System.out.println("\t\t[SampleLoginModule] " +
"authentication failed");
succeeded = false;
username = null;
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
if (!usernameCorrect) {
throw new FailedLoginException("User Name Incorrect");
} else {
throw new FailedLoginException("Password Incorrect");
}
}
}
/**
* This method is called if the LoginContext's
* overall authentication succeeded
* (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
* succeeded).
*
* If this LoginModule's own authentication attempt
* succeeded (checked by retrieving the private state saved by the
* <code>login</code> method), then this method associates a
* <code>SamplePrincipal</code>
* with the <code>Subject</code> located in the
* <code>LoginModule</code>. If this LoginModule's own
* authentication attempted failed, then this method removes
* any state that was originally saved.
*
* @exception LoginException if the commit fails.
*
* @return true if this LoginModule's own login and commit
* attempts succeeded, or false otherwise.
*/
public boolean commit() throws LoginException {
if (succeeded == false) {
return false;
} else {
// add a Principal (authenticated identity)
// to the Subject
// assume the user we authenticated is the SamplePrincipal
userPrincipal = new SamplePrincipal(username);
if (!subject.getPrincipals().contains(userPrincipal))
subject.getPrincipals().add(userPrincipal);
if (debug) {
System.out.println("\t\t[SampleLoginModule] " +
"added SamplePrincipal to Subject");
}
// in any case, clean out state
username = null;
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
commitSucceeded = true;
return true;
}
}
/**
* This method is called if the LoginContext's
* overall authentication failed.
* (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
* did not succeed).
*
* If this LoginModule's own authentication attempt
* succeeded (checked by retrieving the private state saved by the
* <code>login</code> and <code>commit</code> methods),
* then this method cleans up any state that was originally saved.
*
* @exception LoginException if the abort fails.
*
* @return false if this LoginModule's own login and/or commit attempts
* failed, and true otherwise.
*/
public boolean abort() throws LoginException {
if (succeeded == false) {
return false;
} else if (succeeded == true && commitSucceeded == false) {
// login succeeded but overall authentication failed
succeeded = false;
username = null;
if (password != null) {
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
}
userPrincipal = null;
} else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
/**
* Logout the user.
*
* This method removes the <code>SamplePrincipal</code>
* that was added by the <code>commit</code> method.
*
* @exception LoginException if the logout fails.
*
* @return true in all cases since this <code>LoginModule</code>
* should not be ignored.
*/
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
succeeded = false;
succeeded = commitSucceeded;
username = null;
if (password != null) {
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
}
userPrincipal = null;
return true;
}
}
SamplePrincipal.java
package sample.principal;
import java.security.Principal;
/**
* This class implements the <code>Principal</code> interface
* and represents a Sample user.
*
* Principals such as this <code>SamplePrincipal</code>
* may be associated with a particular <code>Subject</code>
* to augment that <code>Subject</code> with an additional
* identity. Refer to the <code>Subject</code> class for more information
* on how to achieve this. Authorization decisions can then be based upon
* the Principals associated with a <code>Subject</code>.
*
* @see java.security.Principal
* @see javax.security.auth.Subject
*/
public class SamplePrincipal implements Principal, java.io.Serializable {
/**
* @serial
*/
private String name;
/**
* Create a SamplePrincipal with a Sample username.
*
* @param name the Sample username for this user.
*
* @exception NullPointerException if the <code>name</code>
* is <code>null</code>.
*/
public SamplePrincipal(String name) {
if (name == null)
throw new NullPointerException("illegal null input");
this.name = name;
}
/**
* Return the Sample username for this <code>SamplePrincipal</code>.
*
* @return the Sample username for this <code>SamplePrincipal</code>
*/
public String getName() {
return name;
}
/**
* Return a string representation of this <code>SamplePrincipal</code>.
*
* @return a string representation of this <code>SamplePrincipal</code>.
*/
public String toString() {
return("SamplePrincipal: " + name);
}
/**
* Compares the specified Object with this <code>SamplePrincipal</code>
* for equality. Returns true if the given object is also a
* <code>SamplePrincipal</code> and the two SamplePrincipals
* have the same username.
*
* @param o Object to be compared for equality with this
* <code>SamplePrincipal</code>.
*
* @return true if the specified Object is equal equal to this
* <code>SamplePrincipal</code>.
*/
public boolean equals(Object o) {
if (o == null)
return false;
if (this == o)
return true;
if (!(o instanceof SamplePrincipal))
return false;
SamplePrincipal that = (SamplePrincipal)o;
if (this.getName().equals(that.getName()))
return true;
return false;
}
/**
* Return a hash code for this <code>SamplePrincipal</code>.
*
* @return a hash code for this <code>SamplePrincipal</code>.
*/
public int hashCode() {
return name.hashCode();
}
}
The Login Configuration
JAAS authentication is performed in a pluggable fashion, so applications can remain independent from underlying authentication technologies. A system administrator determines the authentication technologies, or LoginModules, to be used for each application and configures them in a login Configuration. The source of the configuration information (for example, a file or a database) is up to the current javax.security.auth.login.Configuration implementation. The default Configuration
implementation from Oracle reads configuration information from configuration files, as described in the ConfigFile class.
See Appendix B: JAAS Login Configuration File for information as to what a login configuration file is, what it contains, and how to specify which login configuration file should be used.
The Login Configuration File for the JAAS Authentication Tutorial
As noted, the login configuration file we use for this tutorial,
sample_jaas.config
, contains just one entry, which is
Sample {
sample.module.SampleLoginModule required debug=true;
};
This entry is named "Sample" and that is the name that our tutorial application, SampleAcn
, uses to refer to this entry. The entry specifies that the LoginModule to be used to do the user authentication is the SampleLoginModule
in the sample.module
package and that this SampleLoginModule
is required to "succeed" in order for authentication to be considered successful. The SampleLoginModule
succeeds only if the name and password supplied by the user are the one it expects ("testUser" and "testPassword", respectively).
The SampleLoginModule
also defines a "debug" option that can be set to true
as shown. If this option is set to true,
SampleLoginModule
outputs extra information about the progress of authentication. A LoginModule can define as many options as it wants. The LoginModule documentation should specify the possible option names and values you can set in your configuration file.
Running the JAAS Authentication Tutorial Code
To execute our JAAS authentication tutorial code, all you have to do is
-
Place the following file into a directory:
- sample_jaas.config login configuration file (see The Login Configuration File for the JAAS Authentication Tutorial)
-
Create a subdirectory named sample of that top-level directory, and place the following into it (note the
SampleAcn
and MyCallbackHandler classes, both inSampleAcn.java
, are in a package namedsample
):SampleAcn.java
application source file
-
Create a subdirectory of the
sample
directory and name itmodule
. Place the following into it (note theSampleLoginModule
class is in a package namedsample.module
):SampleLoginModule.java
source file
- Create another subdirectory of the
sample
directory and name itprincipal
. Place the following into it (note theSamplePrincipal
class is in a package namedsample.principal
):SamplePrincipal.java
source file
-
While in the top-level directory, compile
SampleAcn.java
,SampleLoginModule.java
, andSamplePrincipal.java
:javac --enable-preview -source 24 sample/SampleAcn.java sample/module/SampleLoginModule.java sample/principal/SamplePrincipal.java
(Type all that on one line.)
- Execute the
SampleAcn
application, specifying- by
-Djava.security.auth.login.config==sample_jaas.config
that the login configuration file to be used issample_jaas.config
.
- by
The following is the full command:
java --enable-preview -Djava.security.auth.login.config==sample_jaas.config sample.SampleAcn
Note:
If you use a single equals sign (=
) with the
java.security.auth.login.config
system property (instead of a
double equals sign (==
)), then the configurations specified by both
this system property and the java.security
file are
used.You will be prompted for your user name and password, and the SampleLoginModule
specified in the login configuration file will check to ensure these are correct. The SampleLoginModule
expects testUser
for the user name and testPassword
for the password.
You will see some messages output by SampleLoginModule
as a result of the debug
option being set to true
in the login configuration file. Then, if your login is successful, you will see the following message output by SampleAcn:
Authentication succeeded!
If the login is not successful (for example, if you misspell the password), you will see
Authentication failed:
followed by a reason for the failure. For example, if you mistype the password, you may see a message like the following:
Authentication failed:
Password Incorrect
SampleAcn gives you three chances to successfully log in.
The sample prints output similar to the following:
$ java --enable-preview -Djava.security.auth.login.config==sample_jaas.config sample.SampleAcn
user name: testUser
password: testPassword
[SampleLoginModule] user entered user name: testUser
[SampleLoginModule] user entered password: testPassword
[SampleLoginModule] authentication succeeded
[SampleLoginModule] added SamplePrincipal to Subject
Authentication succeeded!
Authenticated user has the following Principals:
SamplePrincipal: testUser
User has 0 Public Credential(s)
Current subject: Subject:
Principal: SamplePrincipal: testUser
Adding principal -1501634219
Authenticated user has the following Principals:
Adding principal -1164915804
Adding principal 304653985
Adding principal -1764420412
Current subject: Subject:
Principal: SamplePrincipal: testUser
Principal: SamplePrincipal: -1501634219
Principal: SamplePrincipal: -1164915804
Principal: SamplePrincipal: 304653985
Principal: SamplePrincipal: -1764420412