You may read the original
JAAS documentation
first. It is recommended to use JAAS as the primary authentication architecture for
Java applications. You only need to use the classes from the ch.odi.jaaspam
package. If, however, for any reason you would like to interface with PAM
directly, please refer to the respective documentation, now.
First a JAAS configuration is needed. Create a JAAS config file that uses the
ch.odi.jaaspam.PamLoginModule
login module. This login module
forwards all JAAS authentication communication to PAM. It must be passed a parameter
service
with the name of the PAM service to use.
pam-sample { ch.odi.jaaspam.PamLoginModule required service=login; # your own login modules };
The location of the JAAS config file must be passed as a system property to the JVM
java.security.auth.login.config
.
In a J2EE application server scenario there may be other options to provide the JAAS configurations. In JBoss, for example, you would typically do this by registering special MBeans.
If you need to associate additional role Principals with the Subject, you should implement
an additional custom login module. Then specify it after the PamLoginModule
as optional
. Your login module will get access to the Subject and PamPrincipal,
allowing you to do whatever you need.
To create the JAAS LoginContext two things are required:
The config name is the one specified in your JAAS config file (pam-sample
in
the example above).
The callback handler is a class implementing the
javax.security.auth.callback.CallbackHandler
interface.
The callback handler will receive all JAAS events and then feed the results back to JAAS.
Typically the callback handler will present a dialog to the user and retrieve the
authenticaion credentials requested by JAAS. It will then hand the credentials back to JAAS.
package ch.odi.jaaspam; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.security.Principal; import java.util.Iterator; import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.TextInputCallback; import javax.security.auth.callback.TextOutputCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; /** * Run as: * LD_LIBRARY_PATH=./target/native java -cp target/classes -Djava.security.auth.login.config=src/conf/sample_jaas.config ch.odi.jaaspam.TestJaas * * @author Ortwin Glück */ public class TestJaas { /** * */ public TestJaas() { try { LoginContext lc = new LoginContext("pam-sample", new MyCallbackHandler()); lc.login(); System.out.print("Authenticated principals: "); Subject subject = lc.getSubject(); Set principals = subject.getPrincipals(); Iterator i = principals.iterator(); while (i.hasNext()) { Principal p = (Principal) i.next(); System.out.print(p.getName()); System.out.print(" "); } System.out.println(""); } catch (LoginException e) { System.out.println("Authentication failed"); } } public static void main(String[] args) { new TestJaas(); } private class MyCallbackHandler implements CallbackHandler { private BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); 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 TextInputCallback) { TextInputCallback tic = (TextInputCallback) callbacks[i]; System.err.print(tic.getPrompt()); System.err.flush(); tic.setText(read()); } else if (callbacks[i] instanceof NameCallback) { // prompt the user for a username NameCallback nc = (NameCallback)callbacks[i]; // ignore the provided defaultName System.err.print(nc.getPrompt()); System.err.flush(); nc.setName(read()); } 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(read().toCharArray()); } else { throw new UnsupportedCallbackException (callbacks[i], "Unrecognized Callback"); } } } private String read() throws IOException { String line = r.readLine(); return line; } } }
The above program will use the pam-sample configuration to authenticate a user.
The source code is in src/test/
.
The dynamic loader must also be able to find our native library that connects PAM to
Java. You can either copy libjaaspam.so
to /usr/local/lib
and run ldconfig
or set LD_LIBRARY_PATH=./target/native
to
the directory where libjaaspam.so
resides.
The JAAS configuration file must be passed to the JVM with the -D
option
in the system property java.security.auth.login.config
:
-Djava.security.auth.login.config=src/conf/sample_jaas.config
The complete call of the sample program is then:
LD_LIBRARY_PATH=./target/native java
-cp target/classes -Djava.security.auth.login.config=src/conf/sample_jaas.config
ch.odi.jaaspam.TestJaas