As JAAS has been included as part of JRE core since Java 1.4, it should be quite prevalent by now. However, I cannot seem to find many online resources that detail the steps involved to set up JAAS with Tomcat. Here, I outline these steps from a beginner’s perspective.
Step 1 – Implement the JASS interfaces.
The basic interfaces to be implemented are
Principal
– there should be two implementations of this interface: One representing the user; another one (or more) represent the roles. The important method of this interface is getName()
which returns the name of the user or role that it represents. LoginModule
– this is the main class that usually serves as the wrapper for customised authentication logic. The login()
method is where the customised login logic should be invoked.
Once implemented, JAR up these classes including the customised login classes and put it in $CATALINA_BASE/lib
.
See the code segments at the end for example implementations.
Step 2 – Configure Tomcat Server
Edit $CATALINA_BASE/conf/server.xml
to put in the JAASRealm configuration. As instructed by Tomcat’s Realm-HOW-TO documentation, this can be done at various levels - <Engine>
, <Host>
or <Context>
. I added it inside the <Host>
tag:
...
Notice the userClassNames
and roleClassNames
are the classes that were created in step 1.
Step 3 – The JRE login configuration
Create a
JAAS Login Configuration file. I called it
jaas.config
and put it under
$CATALINA_BASE/conf
:
acczk {
com.laws.acc.jaas.AccLoginModule required;
};
There are two ways to configure the JRE to load the login ocnfiguration file. I prefer to specify it in JAVA_OPTS
. For Tomcat, this means to create a setenv.sh
or setenv.bat
in the $CATALINA_BASE/bin
directory. Here is my setenv.bat
:
set JAVA_OPTS=-Djava.security.auth.login.config==C:/Tools/apache-tomcat-7.0.2/conf/jaas.config
Step 4 – The Login Form
The login method can be BASIC or FORM-based. I prefer FORM. A vanilla HTML form should do. I happen to be using ZK so my HTML login form is in the file login.zul
:
Welcome to ${c:l('client_name')}!
import com.laws.acc.ws.*;
String errMsg = null;
LoginResponse loginResponse=AccSoapClientProxy.getLoginResponse();
if(loginResponse!=null) {
switch(loginResponse.getResultCode()) {
case ResultCode.NOT_LOGGED_IN:
errMsg="Not logged in.";
break;
case ResultCode.CONNECTION_ERROR:
errMsg="Cannot login to server:\n"+loginResponse.getErrorDescription();
break;
default:
errMsg="Login failure:\n"+loginResponse.getErrorDescription();
}
}
Username :
Password :
${errMsg}
j_username.focus();
The important part here is the HTML form employing
j_username
,
j_password
and
j_security_check
.
Step 5 - Configure web.xml
This is standard configuration to enable security constraints.
ACC Security Constraint
Protected Area
/index.zul
/secure/*
DELETE
GET
POST
PUT
Acc User
Generic authenticated ACC user
Acc User
FORM
Form-Based Authentication Area
/login.zul
/login.zul
120
My JAAS implementation files:
AccLoginModule.java
:
package com.laws.acc.jaas;
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.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import com.laws.acc.ws.AccSoapClientProxy;
import com.laws.acc.ws.LoginResponse;
public class AccLoginModule implements LoginModule {
// initial state
private Subject subject;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
private AccPrincipal principal;
private boolean committed = false;
@Override
public boolean abort() throws LoginException {
if(!committed)
return false;
if(principal!=null) {
logout();
principal=null;
}
return true;
}
@Override
public boolean commit() throws LoginException {
if (principal == null){
return false;
}
if (!subject.getPrincipals().contains(principal))
subject.getPrincipals().add(principal);
// hardcoded role assignment.
AccRole role=new AccRole();
if(!subject.getPrincipals().contains(role))
subject.getPrincipals().add(role);
committed = true;
return (true);
}
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
}
@Override
public boolean login() throws LoginException {
if (callbackHandler == null)
throw new LoginException("No CallbackHandler specified");
Callback callbacks[] = new Callback[2];
callbacks[0] = new NameCallback("Username: ");
callbacks[1] = new PasswordCallback("Password: ", false);
// Interact with the user to retrieve the username and password
String username = null;
String password = null;
try {
callbackHandler.handle(callbacks);
username = ((NameCallback) callbacks[0]).getName();
password = new String(((PasswordCallback) callbacks[1]).getPassword());
LoginResponse response = AccSoapClientProxy.login(username,password);
if (response.getResultCode()!=0)
return false;
principal = new AccPrincipal(response);
return true;
} catch (IOException e) {
throw new LoginException(e.toString());
} catch (UnsupportedCallbackException e) {
throw new LoginException(e.toString());
}
}
@Override
public boolean logout() throws LoginException {
committed=false;
subject.getPrincipals().remove(principal);
return false;
}
}
AccPrincipal.java
:
package com.laws.acc.jaas;
import java.io.Serializable;
import java.security.Principal;
import com.laws.acc.ws.*;
public class AccPrincipal implements Principal, Serializable {
/**
*
*/
private static final long serialVersionUID = 5002820876845306935L;
private LoginResponse loginResponse=null;
public AccPrincipal(LoginResponse lr) {
this.loginResponse=lr;
}
@Override
public String getName() {
return loginResponse.getUserName();
}
@Override
public boolean equals(Object other) {
if(other==null) return false;
if(other==this) return true;
if(other instanceof AccPrincipal) {
AccPrincipal that=(AccPrincipal) other;
return loginResponse.getUserId()== that.getLoginResponse().getUserId();
} else
return false;
}
public LoginResponse getLoginResponse() {
return loginResponse;
}
public void setLoginResponse(LoginResponse loginResponse) {
this.loginResponse = loginResponse;
}
@Override
public int hashCode() {
return loginResponse.getUserId();
}
}
AccRole.java
:
package com.laws.acc.jaas;
import java.security.Principal;
public class AccRole implements Principal {
@Override
public String getName() {
return "Acc User";
}
@Override
public boolean equals(Object other) {
if (other==null) return false;
if(other==this) return true;
if(other instanceof AccRole) {
return this.getName().equals(((AccRole) other).getName());
} else
return false;
}
@Override
public int hashCode() {
return getName().hashCode();
}
}