Playing with JAAS

Just playing with JAAS the other day. Basically its a general set of interfaces and classes (framework) provided by Java to perform authorization and authentication.

  1. Here is the top level code used for authorization using JAAS.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    package com.jassdemo;
     
    import java.security.Principal;
    import java.util.Set;
    import javax.security.auth.Subject;
    import javax.security.auth.login.LoginContext;
    import javax.security.auth.login.LoginException;
    import com.handler.DummyCallbackHandler;
     
    public class JaasTest {
     
    	public static void main(String args[]) {
    		System.setProperty("java.security.auth.login.config", "jaas.config");	//config file!		
    		LoginContext lc = null;
    		try {
    			lc = new LoginContext("Example", new DummyCallbackHandler("username","password"));
    			lc.login();	        
    			Subject sub=lc.getSubject();
    			Set<Principal> principals=sub.getPrincipals();
    			Set<Object> credentials=sub.getPublicCredentials();	        
    			System.out.println(principals);
    			System.out.println(credentials);
    		} catch (LoginException e) {
    			// Authentication failed.
    			e.printStackTrace();
    		}		
    		// Authentication successful, we can now continue.
    		// We can use the returned Subject if we like.
    		Subject sub = lc.getSubject();	    
    	}
     
    }
  2. Create a jaas.config file that contains names of modules that are responsible for authentication. “Example” represents a set of LoginModules used to authenticate someone (called a Subject). jaas.config should be in the classpath. We use this name when creating a new LoginContext(String name, CallbackHandler callbackHandler).
    Example {
      com.module.DummyLoginModule optional username="username" password="password";
      com.module.AlwaysLoginModule required;
    };
  3. Every source that you wish to authenticate against will have its own LoginModule. Your login module implements the LoginModule interface. You have some built in ones, make sure you check if any of them work for you. However, if you want to authenticate against some custom data source (webservice? DB? etc) you would need to create your own modules. In case of my example i have a AlwaysLoginModule (which always logs in irrespective of username/pass) and a DummyLoginModule which authenticates against a hardcoded user/pass.

    Here is a brief description of methods in a module.

    • initialize() method initialize’s objects for the LoginModule. Parameters passed in the Example config can be read here.
    • login() method is where authentication happens. Principal and Credential objects are initialized.
    • commit(). One config can have multiple LoginModules. commit is called if LoginContext’s overall authentication succeeded.  In the example above, since DummyLoginModule is optional, even if it doesn’t succeed the overall authentication will succeed if AlwaysLoginModule succeeds. So commit will be called.
    • abort() One config can have multiple LoginModules. abort is called if LoginContext’s overall authentication failed.  In the example above, since AlwaysLoginModule is required, if it doesn’t succeed the overall authentication will fail and abort will be called.
    • logout() method uninitialize’s objects within the LoginModule.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    
    package com.module;
     
    import java.util.Map;
     
    import javax.security.auth.Subject;
    import javax.security.auth.callback.CallbackHandler;
    import javax.security.auth.login.LoginException;
    import javax.security.auth.spi.LoginModule;
     
    import com.credentials.MyUserCredentials;
    import com.principal.MyUserPrincipal;
     
    public class AlwaysLoginModule implements LoginModule {
     
    	private Subject subject;
    	private CallbackHandler callbackHandler;
    	private Map<String, ?> sharedState;
    	private Map<String, ?> options;
     
    	private MyUserPrincipal principal;
    	private MyUserCredentials credentials;
    	private boolean success;
     
    	@Override
    	public void initialize(Subject subject, CallbackHandler callbackHandler,
    			Map<String, ?> sharedState, Map<String, ?> options) {
    		this.subject = subject;
    		this.callbackHandler = callbackHandler;
    		this.sharedState = sharedState;
    		this.options = options;	
     
    		success = false;
    	}
     
    	@Override
    	public boolean login() throws LoginException {
     
    		// always login success!
    		// add these if commit was called!
    		principal=new MyUserPrincipal("alwaysloggedinusername");
    		credentials=new MyUserCredentials();
    		credentials.put("guestpermission", true);
     
    		success = true;
     
    		return true;
     
    	}
     
    	@Override
    	public boolean commit() throws LoginException {
     
    		if ( success ) {
     
    			subject.getPrincipals().add(principal);
    			subject.getPublicCredentials().add(credentials);
     
    			return true;
    		}
     
    		return false;
    	}
     
    	@Override
    	public boolean abort() throws LoginException {
     
    		if ( success ) {
     
    			callbackHandler = null;
    			sharedState = null;
    			options = null;
     
    			subject.getPrincipals().remove(principal);
    			subject.getPublicCredentials().remove(credentials);
     
    			principal = null;
    			credentials = null;
     
    			// TODO Auto-generated method stub
    			return true;
    		}
     
    		return false;
     
    	}
     
    	@Override
    	public boolean logout() throws LoginException {
     
    		callbackHandler = null;
    		sharedState = null;
    		options = null;
     
    		success = false;
     
    		subject.getPrincipals().remove(principal);
    		subject.getPublicCredentials().remove(credentials);
     
    		principal = null;
    		credentials = null;
     
    		return true;
     
    	}
     
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    
    package com.module;
     
    import java.io.IOException;
    import java.util.Map;
     
    import javax.security.auth.Subject;
    import javax.security.auth.callback.Callback;
    import javax.security.auth.callback.CallbackHandler;
    import javax.security.auth.callback.UnsupportedCallbackException;
    import javax.security.auth.login.LoginException;
    import javax.security.auth.spi.LoginModule;
     
    import com.callback.NamePassCallback;
    import com.credentials.MyUserCredentials;
    import com.principal.MyUserPrincipal;
    import com.principal.MyUserRole;
     
    public class DummyLoginModule implements LoginModule {
     
    	private Subject subject;
    	private CallbackHandler callbackHandler;
    	private Map<String, ?> sharedState;
    	private Map<String, ?> options;
     
    	private MyUserPrincipal principal;
    	private MyUserCredentials credentials;
    	private boolean success;
     
    	@Override
    	public void initialize(Subject subject, CallbackHandler callbackHandler,
    			Map<String, ?> sharedState, Map<String, ?> options) {
    		this.subject = subject;
    		this.callbackHandler = callbackHandler;
    		this.sharedState = sharedState;
    		this.options = options;	
    		success = false;
    	}
     
    	@Override
    	public boolean login() throws LoginException {
     
    		if ( callbackHandler == null ) {
    			throw new LoginException("no handler");
    		}
     
    		// Callback is just like a VO object, that the handler will populate!
    		NamePassCallback namePassCallback=new NamePassCallback("NamePassCallback Prompt!!");
    		Callback[] callbacks = new Callback[] { namePassCallback };
     
    		try {
     
    			callbackHandler.handle(callbacks);
     
    		} catch (IOException e) {
     
    			throw new LoginException("handler io error");
     
    		} catch (UnsupportedCallbackException e) {
     
    			throw new LoginException("unsupported callback error");
     
    		}
     
    		String username = namePassCallback.getUser();
    		String password = new String(namePassCallback.getPass());
     
    		if ( options.get("username").equals(username) && options.get("password").equals(password) ) {
     
    			// add these if commit was called!
    			principal=new MyUserPrincipal(username);
    			credentials=new MyUserCredentials();
    			credentials.put("allpermission", true);
    			credentials.put("grantpermission", true);
     
    			success = true;
     
    		} else {
     
    			throw new LoginException("incorrect username/password");
     
    		}
     
    		return true;
    	}
     
    	@Override
    	public boolean commit() throws LoginException {
     
    		if ( success ) {
     
    			subject.getPrincipals().add(principal);
    			subject.getPublicCredentials().add(credentials);
     
    			return true;
    		}
     
    		return false;
    	}
     
    	@Override
    	public boolean abort() throws LoginException {
     
    		if ( success ) {
     
    			callbackHandler = null;
    			sharedState = null;
    			options = null;
     
    			principal = null;
    			credentials = null;
     
    			return true;
    		}
     
    		return false;
     
    	}
     
    	@Override
    	public boolean logout() throws LoginException {
     
    		callbackHandler = null;
    		sharedState = null;
    		options = null;
     
    		success = false;
     
    		subject.getPrincipals().remove(principal);
    		subject.getPublicCredentials().remove(credentials);
     
    		principal = null;
    		credentials = null;
     
    		return true;
     
    	}
     
    }
  4. LoginModules use CallbackHandlers to get authentication data from users. We use Callback objects to get data out of handlers. Your handlers need to know what callbacks need to be handled, put data into them and send them back. Basically, think of Callback as data transfer objects that the LoginModule sends to the CallbackHandler, which are populated by the handler and sent back to LoginModule for use.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    
    package com.handler;
     
    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.UnsupportedCallbackException;
     
    import com.callback.NamePassCallback;
     
    public class DummyCallbackHandler implements CallbackHandler {
     
    	String user;
    	String pass;
     
    	public DummyCallbackHandler(String user, String pass) {
    		this.user = user;
    		this.pass = pass;
    	}
     
    	@Override
    	public void handle(Callback[] callbacks) throws UnsupportedCallbackException {		
    		for ( int i = 0; i < callbacks.length; i++ ) {			 
    			if (callbacks[i] instanceof NamePassCallback) {				
    				NamePassCallback namepassCallback = (NamePassCallback)callbacks[i];
    				System.out.println("No need for prompt, we'll just use values in: " + namepassCallback.getPrompt());
    				namepassCallback.setUser(user);
    				namepassCallback.setPass(pass);			 
    			} else if (callbacks[i] instanceof NameCallback) {				
    				NameCallback nameCallback = (NameCallback)callbacks[i];
    				nameCallback.setName(user);			 
    			} else if (callbacks[i] instanceof PasswordCallback) {				
    				PasswordCallback passCallback = (PasswordCallback)callbacks[i];
    				passCallback.setPassword(pass.toCharArray());			 
    			} else {				
    				throw new UnsupportedCallbackException(callbacks[i], "UnsupportedCallbackException");				
    			}	
    		}	
    	}
    }

You can download my JAAS demo code here. There is this great post on Jaas in java world titled “All that JAAS” that was very helpful.