JAASUserPrincipal.java
package com.rainyday.server.login;
import java.io.Serializable;
import java.security.Principal;
/**
* @author semika
*
*/
public class JAASUserPrincipal implements Principal, Serializable {
private String name;
/**
* @param name
*/
public JAASUserPrincipal(String name) {
if (name == null) {
throw new NullPointerException("NULL user name");
}
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return "UserPrincipal [name=" + name + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
JAASUserPrincipal other = (JAASUserPrincipal) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
JAASRolePrincipal.java
package com.rainyday.server.login;
import java.io.Serializable;
import java.security.Principal;
/**
* @author semika
*
*/
public class JAASRolePrincipal implements Principal, Serializable {
private String name;
/**
* @param name
*/
public JAASRolePrincipal(String name) {
if (name == null) {
throw new NullPointerException("NULL role name");
}
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return "JASSRolePrincipal [name=" + name + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
JAASRolePrincipal other = (JAASRolePrincipal) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}JAASPasswordPrincipal.java
package com.rainyday.server.login;
import java.io.Serializable;
import java.security.Principal;
/**
* @author semika
*
*/
public class JAASPasswordPrincipal implements Principal, Serializable {
private String name;
/**
* @param name
*/
public JAASPasswordPrincipal(String name) {
if (name == null) {
throw new NullPointerException("NULL password.");
}
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
JAASPasswordPrincipal other = (JAASPasswordPrincipal) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "JAASPasswordPrincipal [name=" + name + "]";
}
}
rainyDay {
com.rainyday.server.login.JAASLoginModule required
dbDriver="com.mysql.jdbc.Driver"
dbURL="jdbc:mysql://localhost/rainyday"
dbUser="root"
dbPassword="abc123"
userQuery="select username from secu_user where secu_user.username=? and secu_user.password=?"
roleQuery="select secu_user_role.rolename from secu_user, secu_user_role "
+ "where secu_user.username=secu_user_role.username and secu_user.username=?"
debug=true;
};
‘jass.config’ file should have this similar format. In addition to the login module declaration, You can declare options as your wish. These options are made available by login module with ‘options’ map in initialize() method argument.
Additionally, We should tell the tomcat, Where to locate the ‘jaas.config’ file by adding it’s path to JAVA_OPTS environment variable. I have added this into ‘catalina.sh’ file under $CATALINA_HOME/bin as follows.
JAVA_OPTS=”$JAVA_OPTS -Djava.security.auth.login.config==../conf/jaas.config”
Next, You need to declare the JAASRealm configurations. You can add a new ‘Realm’ entry into the server.xml file under $CATALINA_HOME/conf. In our tutorial, the ‘Realm’ entry is as follows.
<Realm className="org.apache.catalina.realm.JAASRealm" appName="rainyDay" userClassNames="com.rainyday.server.login.JASSUserPrincipal,com.rainyday.server.login.JAASPasswordPrincipal" roleClassNames="com.rainyday.server.login.JASSRolePrincipal"/>
For apache tomcat’s realm configuration, You can view this documentation. The complete source code for our jaas login module.
JAASLoginModule.java
package com.rainyday.server.login;
import java.io.IOException;
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;
/**
* @author semika
*
*/
public class JAASLoginModule implements LoginModule {
private static Logger LOGGER = Logger.getLogger(JAASLoginModule.class);
// initial state
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;
//user principle
private JAASUserPrincipal userPrincipal = null;
private JAASPasswordPrincipal passwordPrincipal = null;
public JAASLoginModule() {
super();
}
@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;
debug = "true".equalsIgnoreCase((String)options.get("debug"));
}
@Override
public boolean login() throws LoginException {
if (callbackHandler == null){
throw new LoginException("Error: no CallbackHandler available " +
"to garner authentication information from the user");
}
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 (debug) {
LOGGER.debug("Username :" + username);
LOGGER.debug("Password : " + password);
}
if (username == null || password == null) {
LOGGER.error("Callback handler does not return login data properly");
throw new LoginException("Callback handler does not return login data properly");
}
if (isValidUser()) { //validate user.
succeeded = true;
return true;
}
} catch (IOException e) {
e.printStackTrace();
} catch (UnsupportedCallbackException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean commit() throws LoginException {
if (succeeded == false) {
return false;
} else {
userPrincipal = new JAASUserPrincipal(username);
if (!subject.getPrincipals().contains(userPrincipal)) {
subject.getPrincipals().add(userPrincipal);
LOGGER.debug("User principal added:" + userPrincipal);
}
passwordPrincipal = new JAASPasswordPrincipal(new String(password));
if (!subject.getPrincipals().contains(passwordPrincipal)) {
subject.getPrincipals().add(passwordPrincipal);
LOGGER.debug("Password principal added: " + passwordPrincipal);
}
//populate subject with roles.
List<string> roles = getRoles();
for (String role: roles) {
JAASRolePrincipal rolePrincipal = new JAASRolePrincipal(role);
if (!subject.getPrincipals().contains(rolePrincipal)) {
subject.getPrincipals().add(rolePrincipal);
LOGGER.debug("Role principal added: " + rolePrincipal);
}
}
commitSucceeded = true;
LOGGER.info("Login subject were successfully populated with principals and roles");
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;
}
userPrincipal = null;
} else {
logout();
}
return true;
}
@Override
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
succeeded = false;
succeeded = commitSucceeded;
username = null;
if (password != null) {
for (int i = 0; i < password.length; i++){
password[i] = ' ';
password = null;
}
}
userPrincipal = null;
return true;
}
private boolean isValidUser() throws LoginException {
String sql = (String)options.get("userQuery");
Connection con = null;
ResultSet rs = null;
PreparedStatement stmt = null;
try {
con = getConnection();
stmt = con.prepareStatement(sql);
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 {
con.close();
} catch (SQLException e) {
LOGGER.error("Error when closing connection." + e);
}
}
return false;
}
/**
* Returns list of roles assigned to authenticated user.
* @return
*/
private List<string> getRoles() {
Connection con = null;
ResultSet rs = null;
PreparedStatement stmt = null;
List<string> roleList = new ArrayList<string>();
try {
con = getConnection();
String sql = (String)options.get("roleQuery");
stmt = con.prepareStatement(sql);
stmt.setString(1, username);
rs = stmt.executeQuery();
if (rs.next()) {
roleList.add(rs.getString("rolename"));
}
} 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 {
con.close();
} catch (SQLException e) {
LOGGER.error("Error when closing connection." + e);
}
}
return roleList;
}
/**
* Returns JDBC connection
* @return
* @throws LoginException
*/
private Connection getConnection() throws LoginException {
String dBUser = (String)options.get("dbUser");
String dBPassword = (String)options.get("dbPassword");
String dBUrl = (String)options.get("dbURL");
String dBDriver = (String)options.get("dbDriver");
Connection con = null;
try {
//loading driver
Class.forName (dBDriver).newInstance();
con = DriverManager.getConnection (dBUrl, dBUser, dBPassword);
}
catch (Exception e) {
LOGGER.error("Error when creating database connection" + e);
e.printStackTrace();
} finally {
}
return con;
}
}
<login-config>
<auth-method>FORM</auth-method>
<realm-name>rainyDay</realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/error.jsp</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>*</role-name>
</security-role>
<security-constraint>
<web-resource-collection>
<web-resource-name>Rainy day</web-resource-name>
<url-pattern>/</url-pattern>
<http-method>POST</http-method>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>With the above security constraints, if some request comes to a particular resource in the protected area of the application with out the authentication, the request will be redirected to the ‘login’ page. Next, I will show you the simple HTML form which invokes our login module with the submission of the form.
<form id="loginForm" name="loginForm" method="post" action="j_security_check">
User Name : <input id="username" type="text" name="j_username" class="textbox"></input>
Password : <input id="password" type="password" name="j_password" class="textbox"></input>
<input name="login" type="submit" value="LOGIN" id="submit" class="button blue">
</form>JAASCallbackHandler.java
package com.rainyday.server.login;
import java.io.IOException;
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 org.apache.log4j.Logger;
/**
* @author semika
*
*/
public class JAASCallbackHandler implements CallbackHandler {
private static final Logger LOGGER = Logger.getLogger(JAASCallbackHandler.class);
private String username = null;
private String password = null;
/**
* @param username
* @param password
*/
public JAASCallbackHandler(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
LOGGER.info("Callback Handler invoked ");
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
NameCallback nameCallback = (NameCallback) callbacks[i];
nameCallback.setName(username);
} else if (callbacks[i] instanceof PasswordCallback) {
PasswordCallback passwordCallback = (PasswordCallback) callbacks[i];
passwordCallback.setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException(callbacks[i], "The submitted Callback is unsupported");
}
}
}
}
Next, We have to create an instance of ‘ LoginContext’ to invoke the authentication explicitly.
LoginContext lc = null;
try {
lc = new LoginContext("rainyDay", new JAASCallbackHandler(username, password));
lc.login();
//get the subject.
Subject subject = lc.getSubject();
//get principals
subject.getPrincipals();
LOGGER.info("established new logincontext");
} catch (LoginException e) {
LOGGER.error("Authentication failed " + e);
}
Reference: Java form based authentication from our JCG partner Semika loku kaluge at the Code Box blog.



