Author |
Topic: JAAS in Simple |
|
authen member offline |
|
posts: |
56 |
joined: |
06/05/2006 |
from: |
San Diego, CA |
|
|
|
|
|
JAAS in Simple |
What is JAAS?
JAAS stands for Java Authentication and Authorization Service (JAAS).
|
|
|
|
|
|
|
authen member offline |
|
posts: |
56 |
joined: |
06/05/2006 |
from: |
San Diego, CA |
|
|
|
|
|
The Beauty of JAAS |
The beauty of JAAS is attributed to its simplest layout architecture. No matter how complicated the underlying implementation is, the interface remains essentially the same. Once the JAAS interface is integrated into your business layer, it's a done deal -- regardless of the change and upgrade at the actual authentication layer.
Here is a minimal example to integrate JAAS:
/* JAAS Part I -- Authentication */
LoginContext lc = new LoginContext("myLoginEntity");
try {
lc.login();
} catch (LoginException e) {
throw e;
}
/* JAAS Part II -- Authorization */
Subject sub = lc.getSubject();
Subject.doAs(sub, new MyPrivilegedAction());
That's it. Just that simple!
|
|
|
|
|
|
|
authen member offline |
|
posts: |
56 |
joined: |
06/05/2006 |
from: |
San Diego, CA |
|
|
|
|
|
JAAS Pluggable Login Module |
JAAS implements a Java version of the standard Pluggable Authentication Module (PAM) framework. All login modules are implementing the common interface LoginModule:
+---------------+
| LoginModule | <-- {login,logout,...}
+---------------+
/ \
/ \
+-----------------+ +-----------------+
| MyLoginModule_1 | | MyLoginModule_2 | ...
+-----------------+ +-----------------+
For example, you can use com.sun.security.auth.module.Krb5LoginModule to handle Kerberos authentication to KDC.
|
|
|
|
|
|
|
authen member offline |
|
posts: |
56 |
joined: |
06/05/2006 |
from: |
San Diego, CA |
|
|
|
|
|
Injection of Customized JAAS Login Module |
In order for your customized login module to be injected, you have to tell JAAS the following: Where to find your configuration file; Which login module to load and how it is loaded.
Where to find your configuration file? If you name your JAAS login configuration file as jaas_login.conf and put it under directory c:\temp, then you can instruct your JVM to find it by property setting:
System.setProperty("java.security.auth.login.config", "c:\\temp\\jaas_login.conf");
Which login module to load and how it is loaded? The configuration file has the following structure:
myLoginEntity {
ModuleClass Flag Options;
ModuleClass Flag Options;
...
};
myLoginEntity {
ModuleClass Flag Options;
...
};
...
As an example:
myLoginEntity {
com.sun.security.auth.module.Krb5LoginModule required
principal="myName@MY_REALM"
useTicketCache=true
ticketCache="C:\\temp\\krb5cc_myName"
renewTGT=true
useKeyTab=true
keyTab="C:\\temp\\myName.keytab"
storeKey=true;
};
Which instructs that Krb5LoginModule is to be injected with required flag and the corresponding options: using principal myName@MY_REALM as login name and retrieving TGT ticket from cache C:\temp\krb5cc_myName; if a valid ticket found, no need to proceed. if ticket expired (renewTGT=true) or ticket not found, retrieving the private key from keytab C:\temp\myName.keytab; if key is not found, prompting user for password input; requesting Kerberos authentication to KDC by using the above principal and private key (or password); Confirmed by WireShark traffic KRB5 with AS-REQ/AS-REP storing (storeKey=true) the private key into Subject's private space after successful authentication.
Note: By spec, when multiple mechanisms to retrieve a ticket or key is provided, the preference order looks like this: ---- 1. ticket cache ---- 2. keytab ---- 3. shared state ---- 4. user prompt For example, if "principal" is provided both from config and user specified, the value from config would take precedence. The keyTab's path must be double-quote protected, otherwise exception would be thrown. The back-slash (\) in path must be escaped(\\), otherwise, keyTab would be ignored and the user's password would be used instead.
|
|
|
|
|
|
|
authen member offline |
|
posts: |
56 |
joined: |
06/05/2006 |
from: |
San Diego, CA |
|
|
|
|
|
How to create my own JAAS login module |
package com.myCompany;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.security.Principal;
import com.sun.security.auth.UserPrincipal;
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.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
public class MyLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
private boolean succeeded = false;
private String username;
public MyLoginModule() {
System.out.println("My Login Module - Constructor called");
}
@Override
// Required
public boolean abort() throws LoginException {
System.out.println("My Login Module - abort() called");
return false;
}
@Override
// Required
public boolean commit() throws LoginException {
System.out.println("My Login Module - commit() called");
if(succeeded){
Set princSet = subject.getPrincipals();
Principal principal = new UserPrincipal(username);
princSet.add(principal);
}
return succeeded;
}
@Override
// Required
public void initialize(Subject subject,
CallbackHandler callbackHandler,
Map<String, ?> sharedState,
Map<String, ?> options)
{
System.out.println("My Login Module - initialize() called");
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
succeeded = false;
}
@Override
// Required
public boolean login() throws LoginException
{
System.out.println("My Login Module - login() called");
if (callbackHandler == null) {
throw new LoginException("Oops, callbackHandler is null");
}
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("name:");
callbacks[1] = new PasswordCallback("password:", false);
try {
callbackHandler.handle(callbacks);
} catch (IOException e) {
throw new LoginException(
"Oops, IOException calling handle on callbackHandler");
} catch (UnsupportedCallbackException e) {
throw new LoginException("Oops, UnsupportedCallbackException " +
"calling handle on callbackHandler");
}
NameCallback nameCallback = (NameCallback) callbacks[0];
PasswordCallback passwordCallback = (PasswordCallback) callbacks[1];
// name & password from callback handler
String name = nameCallback.getName();
String password = new String(passwordCallback.getPassword());
// options from configuration settings
String option = (String)options.get("anonymousAllowed");
if(password==null || "".equals(password)){
if( option!=null && option.equalsIgnoreCase("true") ){
System.out.println("Succeeded!");
username = name;
succeeded = true;
return succeeded;
}
}else if(authenticate(name, password)){
System.out.println("Succeeded!");
username = name;
succeeded = true;
return succeeded;
}
System.out.println("Failed!");
succeeded = false;
throw new FailedLoginException("Name and password not matched.");
}
@Override
// Required
public boolean logout() throws LoginException {
System.out.println("My Login Module - logout() called");
return false;
}
private boolean authenticate(String name, String password)
{
/* just an exmaple, you can put whatever your authenticating logic here */
return name.equals(password);
}
}
|
|
|
|
|
|
|
authen member offline |
|
posts: |
56 |
joined: |
06/05/2006 |
from: |
San Diego, CA |
|
|
|
|
|
Stand alone test before let your custom module out |
public static void main(String[] args) throws Exception
{
try{
Subject subject = new Subject();
MyLoginModule myLoginModule = new MyLoginModule();
Map <String, String> map = new HashMap <String, String>();
map.put("anonymousAllowed", "false");
// CallbackHandler
final String name = "Joe";
final String password = "Joe";
CallbackHandler ch = new CallbackHandler(){
public void handle (Callback[] callbacks) throws
UnsupportedCallbackException, IOException
{
for(int j=0; j<callbacks.length; j++) {
Callback callBack = callbacks[j];
// Handles username callback.
if (callBack instanceof NameCallback) {
NameCallback nc = (NameCallback)callBack;
nc.setName(name);
// Handles password callback.
} else if (callBack instanceof PasswordCallback) {
PasswordCallback pc = (PasswordCallback)callBack;
pc.setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException(callBack,
"Call back not supported");
}
}
}
};
myLoginModule.initialize(subject, ch, null, map);
myLoginModule.login();
myLoginModule.commit();
System.out.println(subject);
}catch(Exception e){
e.printStackTrace();
}
}
Here is the output:
My Login Module - Constructor called
My Login Module - initialize() called
My Login Module - login() called
Succeeded!
My Login Module - commit() called
Subject:
Principal: Joe
|
|
|
|
|
|
|
authen member offline |
|
posts: |
56 |
joined: |
06/05/2006 |
from: |
San Diego, CA |
|
|
|
|
|
Inject your own login module into JAAS framework -- Put all pieces together |
Now that you have your own login module ready, you can integrate it into your business layer with the help of configuration file named C:\temp\jaas_login.conf:
myLoginEntity {
com.myCompany.MyLoginModule required
anonymousAllowed=true
debug=true;
};
Integration:
public static void main(String[] args) throws Exception
{
System.setProperty("java.security.auth.login.config",
"C:\\temp\\jaas_login.conf");
try{
// CallbackHandler
final String name = "Joe";
final String password = "Joe";
CallbackHandler ch = new CallbackHandler(){
public void handle (Callback[] callbacks) throws
UnsupportedCallbackException, IOException
{
for(int j=0; j<callbacks.length; j++) {
Callback callBack = callbacks[j];
// Handles username callback.
if (callBack instanceof NameCallback) {
NameCallback nc = (NameCallback)callBack;
nc.setName(name);
// Handles password callback.
} else if (callBack instanceof PasswordCallback) {
PasswordCallback pc = (PasswordCallback)callBack;
pc.setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException(callBack,
"Call back not supported");
}
}
}
};
LoginContext lc = new LoginContext("myLoginEntity", ch);
lc.login();
Subject subject = lc.getSubject();
System.out.println(subject);
}catch(Exception e){
e.printStackTrace();
}
}
Here is the output:
My Login Module - Constructor called
My Login Module - initialize() called
My Login Module - login() called
Succeeded!
My Login Module - commit() called
Subject:
Principal: Joe
|
|
|
|
|
|
|
authen member offline |
|
posts: |
56 |
joined: |
06/05/2006 |
from: |
San Diego, CA |
|
|
|
|
|
JAAS framework is a perfect example of Strategy Design Pattern |
The strategy pattern is a behavioral design pattern. In the strategy pattern, different behaviors are represented as Concrete Strategy classes and they share a common Strategy interface. A Context class contains a reference to the common Strategy interface. By changing the Context's Strategy, different behaviors can be obtained.
+-------------+ +------------+
| Context |* -------------->| Strategy |
+-------------+ +------------+
| # logic() | | # hook(); |
+-------------+ +------------+
^
|
----------------------
| |
+------------+ +------------+
| Strategy1 | | Strategy2 |
+------------+ +------------+
| # hook() | | # hook() |
+------------+ +------------+
The strategy pattern is based on separation principle (in contrast to unification principle as in subclass inheritance) where Strategy specifying hook methods is defined on one side and business logic and user's data are processed inside Context on the other side (with help, for sure, for calling hook methods). Here, LoginContext serves as business logic Context and LoginModule serves as Strategy interface. With different input as to strategy "myLoginEntity" and user's data "CallBack", different authentication behavior can be achieved.
|
|
|
|
|
|
|
|