For a long time I’ve been using FORM based authentication, logging in with the j_security_check, and then using the getRemoteUser() as a starting point for user-related functionality. But I got a bit tired of letting all the control of my login to the j_security_check, as this one is very difficult to customize. I wanted to do a login to the application programmatically, and started to investegate.
My first try was to use the LoginContext (javax.security.auth.login.LoginContext) and do a JAAS login, as I found an article here that describes this in a good way. It seems plain and simple:
In the login-config.xml you create your LoginModule configuration, something like this:
<application-policy name = "myLoginConfiguration">
<authentication>
<login-module code = "org.jboss.security.auth.spi.DatabaseServerLoginModule"
flag = "required">
<module-option name = "unauthenticatedIdentity">guest</module-option>
<module-option name = "dsJndiName">java:/MyDataSourceConfig</module-option>
<module-option name = "principalsQuery">Select passord from users where userid=?</module-option>
<module-option name = "rolesQuery">Select role, 'Roles' from user_roles where userid=?</module-option>
</login-module>
</authentication>
</application-policy>
Imagine when doing a login, we need some way of passing the username and the password to the login module, and this can be done by using a callback handler. In this example I’ve been using a PassiveCallbackHandler, which basically have a constructor for username and password, so that it doesn’t really care where you get the username and password from (could be from the request, a bean etc). For reference I display the PassiveCallbackHandler here:
import java.io.*;
/* JAAS imports */
import javax.security.auth.callback.*;
/**
*
* PassiveCallbackHandler has constructor that takes
* a username and password so its handle() method does
* not have to prompt the user for input.
* Useful for server-side applications.
*
* @author Paul Feuer and John Musser
* @version 1.0
*/
public class PassiveCallbackHandler implements CallbackHandler {
private String username;
char[] password;
/**
*
Creates a callback handler with the give username
* and password.
*/
public PassiveCallbackHandler(String user, String pass) {
this.username = user;
this.password = pass.toCharArray();
}
/**
* Handles the specified set of Callbacks. Uses the
* username and password that were supplied to our
* constructor to popluate the Callbacks.
*
* This class supports NameCallback and PasswordCallback.
*
* @param callbacks the callbacks to handle
* @throws IOException if an input or output error occurs.
* @throws UnsupportedCallbackException if the callback is not an
* instance of NameCallback or PasswordCallback
*/
public void handle(Callback[] callbacks)
throws java.io.IOException, UnsupportedCallbackException
{
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback)callbacks[i]).setName(username);
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback)callbacks[i]).setPassword(password);
} else {
throw new UnsupportedCallbackException(
callbacks[i], "Callback class not supported");
}
}
}
/**
* Clears out password state.
*/
public void clearPassword() {
if (password != null) {
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
}
}
}
And then from java side in your login-method you are told simply to do:
LoginContext lc = new LoginContext("myLoginConfiguration", new PassiveCallbackHandler(user,pass));
try
{
lc.login();
}
catch (LoginException)
{
// Authentication failed.
}
Fine, you think. If you try to get the user from lc.getSubject() it works.
But still, if you try getUserPrincipal() or getRemoteUser(), those are null.
This is because even if you have successfully logged in using jaas, the web container in jboss (tomcat) doesn’t know. And there is no way of “feeding” this knowledge to the web tier, you cannot do a set on the remote user, and there are no magic calls you have forgotten. What to do then??Depending on which jboss version you are running, there are different solutions. If you use 4.2.2 or higher, you are the lucky one. If you use an older version, there’s a longer path to walk.For my part I have upgraded to 4.2.2 (which seems to be running nice), and then you have the luxury of a new class called WebAuthentication (org.jboss.web.tomcat.security.login.WebAuthentication). This is a class that has been done to solve the above issue, namely to do a proper login to the web container. It is found inside the jbossweb-service.jar, so you need to have this one in your classpath. It is VERY simple to use it, just do like this:
WebAuthentication webA = new WebAuthentication(); webA.login(username, password);
And you’re on
Both getUserPrincipal() and getRemoteUser() works. This was something I’ve been waiting for at least, now you can finally have control of the login procedure, not to mention you can
go wherever you want after doing a successful login. (A small pinch in the side to j_security_check
) The only downside I’ve found (this far) is that the 4.2.2 doesn’t support Single-Sign-On using the valve yet( in server.xml: <Valve className=”org.apache.catalina.authenticator.SingleSignOn” /> ). But according to this page this is to be resolved when jboss 5 is released.But then, what about those of you that uses older versions of jboss? Unfortunately, there are no ways (that I have found) which gives you the same effect as with the WebAuthentication. I believe that what you need to do here is to forget about remoteUser and userPrincipal, and play against the loginContext instead. Let’s take an example from jsf, and say that you have a LoginHandler with a method getLoggedOnUsername. Instead of getting the username from externalContext.getRemoteUser(), get it from the loginContext:Old way
public String getLoggedOnUserName()
{
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext externalContext = context.getExternalContext();
String remoteUser = externalContext.getRemoteUser();
if(remoteUser != null)
return remoteUser;
else
return "guest";
}
New way
public String getLoggedOnUserName()
{
String username = lc.getSubject()
if(username != null)
return username;
else
return "guest";
}
This of course assumes that the LoginHandler holds the LoginContext object.Now, for both versions you would need to decide when to login to you application. Are all the pages protected? Or just a subset of them? Are all pages open but there’s additional functionality available when a user is logged in and not a guest? Depending on these answers there are different ways of handling the authentication mechanism. If you use WebAuthentication you can actually let JAAS trigger the login for you, just as we did with j_security_check, only that you can set the action of your form to where you want to go and control the login yourself. Another alternative could be to create a security filter with the url-pattern of your protected resources.
<filter-mapping> <filter-name>SecurityFilter</filter-name> <url-pattern>/protected/*</url-pattern> </filter-mapping>
I haven’t done this myself, but there is a project here that could be a good starting point for this.