Java Authentication and Authorization Service (JAAS) is another implementation of Tomcat Realm ( org.apache.catalina.Realm) , used to authenticate users and identify their security roles.
The value of appName property is passed to the LoginContext (javax.security.auth.login.LoginContext) constructor to specify the name of implemented LoginModule ( javax.security.auth.spi.LoginModule).
LoginModule is a pluggable interface used to provide a particular type of authentication. LoginContext reads the configuration (javax.security.auth.login.Configuration) which specifies the login module (s) used in the login application.
A login configuration contains the following information :
Name {
ModuleClass Flag ModuleOptions;
ModuleClass Flag ModuleOptions;
ModuleClass Flag ModuleOptions;
};
There could be more the one login module in one login configuration.
ModuleClass is the fully qualified class name of the login module. The Flag
values ( Required, Requisite, Sufficient, Optional ) control the behavior of authentication.
ModuleOptions is a space separated list of the login module specific values which are passed directly to the underlying login modules.
Add the following Tomcat JAASRealm config to Tomcat server.xml :
Create a file named jass.config and place it in tomcat/conf directory:
jasslogin{
com.test.secure.TestLoginModule required;
};
Create a file named setenv.bat, add the below config, and place it in tomcat/bin directory
set JAVA_OPTS=-Djava.security.auth.login.config==C:/tomcat/conf/jaas.config
When the configuration is read by the logincontext, the login module is initialized with Subject ( javax.security.auth.Subject) , a call back handler ( javax.security.auth.callback.CallBackHandler) , shared login module state and and LoginModule-specific options.
boolean login() throws LoginException;
Login mothod is the first one invoked by the LoginContext to perform the actual authentication and the return either true or false. If authentication succeeds the method commit gets invoked.
package com.test.secure;
import java.io.IOException;
import java.security.Principal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
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 org.apache.log4j.Logger;
public class TestLoginModule implements LoginModule {
Logger logger = Logger.getLogger(TestLoginModule.class);
public static String USER_QUERY = "select user_name from users where user_name=? and user_pass=?";
public static String ROLE_QUERY = "select role_name from user_roles where user_name=?";
private Subject subject;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
// configurable option
private boolean debug = false;
// the authentication status
private boolean succeeded = false;
private boolean commitSucceeded = false;
// user credentials
private String username = null;
private char[] password = null;
// principals
private TestUserPrincipal testUserPrincipal;
private TestRolePrincipal testRolePrincipal;
private TestPasswordPrincipal testPasswordPrincipal;
@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;
}
@Override
public boolean login() throws LoginException {
if (callbackHandler == null) {
throw new LoginException("call back handler is null");
}
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("username");
callbacks[1] = new PasswordCallback("password: ", false);
try {
callbackHandler.handle(callbacks);
username = ((NameCallback) callbacks[0]).getName();
password = ((PasswordCallback) callbacks[1]).getPassword();
if (username == null || password == null) {
throw new LoginException(
"Callback handler does not return login data properly");
}
logger.info(" username" + username);
logger.info("password" + password);
// authenticate
if (isValidUser()) {
succeeded = true;
return true;
}
} catch (IOException e) {
e.printStackTrace();
} catch (UnsupportedCallbackException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean commit() throws LoginException {
logger.info("committing...");
if (succeeded == false) {
return false;
} else {
testUserPrincipal = new TestUserPrincipal(username);
if (!subject.getPrincipals().contains(testUserPrincipal)) {
subject.getPrincipals().add(testUserPrincipal);
}
/* testPasswordPrincipal = new TestPasswordPrincipal(new String(
password));
if (!subject.getPrincipals().contains(testPasswordPrincipal)) {
subject.getPrincipals().add(testPasswordPrincipal);
}
*/
// populate subject with roles.
// strings
List roles = getRoles(testUserPrincipal);
for (String role : roles) {
testRolePrincipal = new TestRolePrincipal(role);
if (!subject.getPrincipals().contains(testRolePrincipal)) {
subject.getPrincipals().add(testRolePrincipal);
}
}
commitSucceeded = true;
logger.info("Login subject were successfully populated with principals and roles");
logger.info("--------------principals");
logger.info(subject.getPrincipals());
for(Principal p: subject.getPrincipals()){
if(p instanceof TestRolePrincipal){
logger.info(" ROLE: "+p.getName());
}
}
return true;
}
}
@Override
public boolean abort() throws LoginException {
if (succeeded == false) {
return false;
} else if (succeeded == true && commitSucceeded == false) {
succeeded = false;
username = null;
if (password != null) {
password = null;
}
testUserPrincipal = null;
} else {
logout();
}
return true;
}
@Override
public boolean logout() throws LoginException {
subject.getPrincipals().remove(testUserPrincipal);
succeeded = false;
succeeded = commitSucceeded;
username = null;
if (password != null) {
for (int i = 0; i < password.length; i++) {
password[i] = ' ';
password = null;
}
}
testUserPrincipal = null;
return true;
}
private boolean isValidUser() throws LoginException {
Connection connection = null;
ResultSet rs = null;
PreparedStatement stmt = null;
try {
connection = getConnection();
stmt = connection.prepareStatement(USER_QUERY);
stmt.setString(1, username);
stmt.setString(2, new String(password));
rs = stmt.executeQuery();
if (rs.next()) { // User exist with the given user name and
// password.
return true;
}
} catch (Exception e) {
logger.error("Error when loading user from the database " + e);
e.printStackTrace();
} finally {
try {
rs.close();
} catch (SQLException e) {
logger.error("Error when closing result set." + e);
}
try {
stmt.close();
} catch (SQLException e) {
logger.error("Error when closing statement." + e);
}
try {
connection.close();
} catch (SQLException e) {
logger.error("Error when closing connection." + e);
}
}
return false;
}
private List getRoles(TestUserPrincipal user) {
Connection connection = null;
ResultSet rs = null;
PreparedStatement stmt = null;
List roleList = new ArrayList();
try {
connection = getConnection();
stmt = connection.prepareStatement(ROLE_QUERY);
stmt.setString(1, username);
rs = stmt.executeQuery();
while (rs.next()) {
roleList.add(rs.getString("role_name"));
user.addRole(new TestRolePrincipal((rs.getString("role_name"))));
}
} catch (Exception e) {
logger.error("Error when loading user from the database " + e);
e.printStackTrace();
} finally {
try {
rs.close();
} catch (SQLException e) {
logger.error("Error when closing result set." + e);
}
try {
stmt.close();
} catch (SQLException e) {
logger.error("Error when closing statement." + e);
}
try {
connection.close();
} catch (SQLException e) {
logger.error("Error when closing connection." + e);
}
}
return roleList;
}
private Connection getConnection() {
try {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "root", "password");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.test.secure;
import java.security.Principal;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.catalina.Group;
import org.apache.catalina.Role;
import org.apache.catalina.User;
import org.apache.catalina.UserDatabase;
public class TestUserPrincipal implements User {
private String username;
private Set roles = new HashSet();
public TestUserPrincipal(String u){
this.username=u;
}
@Override
public String getName() {
// TODO Auto-generated method stub
return username;
}
@Override
public void addGroup(Group arg0) {
// TODO Auto-generated method stub
}
@Override
public void addRole(Role role) {
roles.add(role);
}
@Override
public String getFullName() {
// TODO Auto-generated method stub
return username;
}
@Override
public Iterator getGroups() {
return null;
}
@Override
public String getPassword() {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterator getRoles() {
return roles.iterator();
}
@Override
public UserDatabase getUserDatabase() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getUsername() {
// TODO Auto-generated method stub
return username;
}
@Override
public boolean isInGroup(Group arg0) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isInRole(Role role) {
if(1==1){
return true;
}
if(!roles.isEmpty()){
Iterator it =roles.iterator();
while(it.hasNext()){
Role rol =(Role)it.next();
if(rol.getName()!=null && rol.getName().equals(role.getName())){
return true;
}
}
}
return false;
}
@Override
public void removeGroup(Group arg0) {
// TODO Auto-generated method stub
}
@Override
public void removeGroups() {
// TODO Auto-generated method stub
}
@Override
public void removeRole(Role arg0) {
// TODO Auto-generated method stub
}
@Override
public void removeRoles() {
roles.clear();
}
@Override
public void setFullName(String arg0) {
setUsername(username);
}
@Override
public void setPassword(String arg0) {
// TODO Auto-generated method stub
}
@Override
public void setUsername(String arg0) {
this.username=arg0;
}
}
package com.test.secure;
import java.io.Serializable;
import java.security.Principal;
import org.apache.catalina.Role;
import org.apache.catalina.UserDatabase;
public class TestRolePrincipal implements Role, Serializable {
private String roleName;
public TestRolePrincipal(String name){
this.roleName=name;
}
@Override
public String getName() {
// TODO Auto-generated method stub
return getRolename();
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return " some role";
}
@Override
public String getRolename() {
// TODO Auto-generated method stub
return roleName;
}
@Override
public UserDatabase getUserDatabase() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setDescription(String arg0) {
}
@Override
public void setRolename(String arg0) {
roleName =arg0;
}
Make sure you jar these three classes in tomcat/lib so Tomcat loads them on startup.
In our case we are using SpringMVC, but for this test, you can use any other request handler or just a plain servlet.
package com.test.secure;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.catalina.Session;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
public class SecureController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest req,
HttpServletResponse res) throws Exception {
ModelAndView m = new ModelAndView("SecureView");
return m;
}
}
Add a security constraint ( org.apache.catalina.deploy. SecurityConstraint) and the set of roles permitted to access the resources to your application web.xml
< security-constraint > < web-resource-collection > < web-resource-name>interdit< /web-resource-name > < url-pattern >/go/*< /url-pattern > < /web-resource-collection > < auth-constraint > < description>tomcat< /description > < role-name>tomcat< /role-name > < /auth-constraint> < /security-constraint>
And add login and login error pages
FORM jasslogin /join.do /joinerror.do
CREATE TABLE `users` (
`user_name` varchar(15) NOT NULL,
`user_pass` varchar(15) NOT NULL,
PRIMARY KEY (`user_name`);
INSERT INTO `users` VALUES ('admin','root'),('role1','root'),('tomcat','tomcat');
CREATE TABLE `user_roles` (
`user_name` varchar(15) NOT NULL,
`role_name` varchar(15) NOT NULL,
PRIMARY KEY (`user_name`,`role_name`);
INSERT INTO `user_roles` VALUES ('tomcat','manager-gui'),('tomcat','manager-jmx'),('tomcat','manager-script'),('tomcat','manager-status'),('tomcat','tomcat');