Update the FormAuthValve to associate the error message the error page should render with the request or session. A valve should not be concerned about rendering.
The FormAuthValve does exactly what you suggest - puts a message (a Throwable) in the session that the error page may render. The problem is that the error page is processed before the Throwable has been put in the session.
If I put a link on the error-page to an error-info-page and if the user then clicks on the link then the error-info-page successfully presents the Throwable.
Since the Valve executes around the" j_security_check" page it cannot have acces to the Throwable before the j_security_check page is invoked.
I was also looking for a solution, since I'd like to present the error mesage in the error page, not in another page linked from the error page.
Then you need to replace the org.apache.catalina.authenticator.FormAuthenticator with your subclass that attaches this info for use in the error page. A valve that implements the org.apache.catalina.Authenticator tagging interface is used as the authenticator for the associated web app.
Dig into it and create a jira feature request issue and whatever solution comes up can be integrated into jboss as an ease of use authenticator for future releases.
I found all the needed code in Apache's CVS. It looks like we have to copy-paste the org/apache/catalina/authenticator/FormAuthenticator.java code into a new Authenticator, and then configure Tomcat to use it. It looks like this is done in the
How can the Authenticators.properties file be overrided in the embedded Tomcat?
most likely you will have to patch JBOSS_HOME/server/instance/deploy/jbossweb-tomcat50.sar/catalina.jar with a patched Authenticators.properties to point out the new FormAuthenticator subclass.
I dislike the idea of overriding the FormAuthenticator class, much nicer to hook into the request pipeline with an independant valve such as the FormAuthValve. For my needs it suffice to:
1. In web.xml set form-error-page to login-error-client-redirect.jsp that does
<%-- do an extra request/response roundtrip --%> <body onLoad="window.location = 'login-error.jsp'"/>.
2. Have login-error.jsp format the LoginException that was set in the session by Scott's FormAuthValve.
The problem with that aproach is that ALL my application is protected: /*
/login-error.jsp wont be displayed since it is protected also.
It can be overriden at the web app using a WEB-INF/context.xml that specifies the valve implementing the Authenticator interface:
<Context> <Valve className="com.acme.authenticator.FormAuthenticator" disableProxyCaching="true" /> </Context>
The Authenticators.properties is loaded using the class path, so a conf/org/apache/catalina/startup/Authenticators.properties may work as an override but I have not tested it.
The window.location trick worked!
I only had to define the error page as "not protected".
This is my web.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <security-constraint> <web-resource-collection> <web-resource-name>All the application</web-resource-name> <url-pattern>/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>Error Page</web-resource-name> <url-pattern>/login-error.jsp</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> </security-constraint> <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/login.jsp</form-login-page> <form-error-page>/login-error-redirect.jsp</form-error-page> </form-login-config> </login-config> <security-role> <role-name>admin</role-name> </security-role> </web-app>
<%-- do an extra request/response roundtrip --%> <body onLoad="window.location = 'login-error.jsp'"/>
An example login-error.jsp:
<h1>Login failed!</h1> Exception: <%= session.getAttribute("j_exception") %>
In real life your LoginModule could throw a LoginException subtype (ie AccountExpiredException or CredentialExpiredException) and you could display a different message for each case in login-error.jsp, using instanceof.
Where in your Custom Login module can/should you throw the exception? I see that getRoleSet throws a LoginException, but not validatePassword...
If you want to throw custom LoginExceptions you should code your LoginModule to extend org.jboss.security.auth.spi.AbstractServerLoginModule instead of using the org.jboss.security.auth.spi.UsernamePasswordLoginModule.
Use the UsernamePasswordLoginModule as an example of how to code your LoginModule.
Get the source code from the CVS and read it.
regarding the window.location-trick:
The only trick here is, that the Valve will fire afterwards the error.jsp gets invoked!! Thus, to grab the exception in j_exception, you have to redirect to the error.jsp or to another jsp once again.
(I've used a separate servlet for the whole login process, which makes everything much more flexible and allows for multiple pages for the whole login process).
Secondly, you don't have to code your own FormAuthValve.
Everything you need is to activate the FormAuthValve delivered right with the jboss classes.
<Context> <Valve className="org.jboss.web.tomcat.security.FormAuthValve"/> <Manager className="org.apache.catalina.session.StandardManager" pathname="" /> </Context>
The FormAuthValve will automatically retrieve an exception thrown in your LoginModule and store it using HttpSession.setAttribute("j_exception", throwable)
If you want to recode FormAuthValve, you'll have to deploy it right where the catalina-libs are (server/default/deploy/jbossweb-tomcat50.sar) and make sure it uses the package org.jboss.web.tomcat.security, because SecurityAssociationActions.getAuthException() is protected.
That's all - after that displaying LoginExceptions in the UI is a breeze.