Introduction

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.

Configuring JAAS

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.

Creating the login context

To create the JAAS LoginContext two things are required:

  1. The JAAS config name
  2. A custom callback handler

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.

Sample code

        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