13 Replies Latest reply on May 18, 2006 4:11 PM by Ricardo Arguello

    FormAuthValve

    Niklas Nobel Newbie

      Hi!

      Im using FORM-BASED authentication and I would like to give the user an explanation to why his login attempt failed. Today my form-error-page just states "login failed - please try again..." :-(

      On the wiki I found an interesting piece of code that just about does it. It is a valve, FormAuthValve, that hooks into the request pipeline and saves a javax.security.auth.login.LoginException in the session should your login module signal such an exception. And the form-error-page just have to pick up and render the exception.

      The problem is that once the FormAuthValve is callbacked the form-error-page has already been processed and been put into the response object. I guess I could patch the response outstream but there has to be a smarter way.

      I use jboss-3.2.6. And I have "registered" the FormAuthValve in /WEB-INF/context.xml as follows

      <Context>
       <Valve className="org.jboss.web.tomcat.security.FormAuthValve"/>
       <Manager className="org.apache.catalina.session.StandardManager"
       pathname="" />
      </Context>
      


      Any comments greatly appreciated! /niklas

        • 1. Re: FormAuthValve
          Scott Stark Master

          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.

          • 2. Re: FormAuthValve
            Niklas Nobel Newbie

            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.

            /niklas

            • 3. Re: FormAuthValve
              Ricardo Arguello Newbie

              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.

              Any ideas?

              Ricardo

              • 4. Re: FormAuthValve
                Scott Stark Master

                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.

                http://jira.jboss.com/jira/browse/JBAS

                • 5. Re: FormAuthValve
                  Ricardo Arguello Newbie

                  Scott,

                  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
                  org/apache/catalina/startup/Authenticators.properties file.

                  How can the Authenticators.properties file be overrided in the embedded Tomcat?

                  Thanks,

                  Ricardo

                  • 6. Re: FormAuthValve
                    Niklas Nobel Newbie

                    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.

                    /niklas

                    • 7. Re: FormAuthValve
                      Ricardo Arguello Newbie

                      The problem with that aproach is that ALL my application is protected: /*

                      /login-error.jsp wont be displayed since it is protected also.

                      • 8. Re: FormAuthValve
                        Scott Stark Master

                        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.


                        • 9. Re: FormAuthValve
                          Ricardo Arguello Newbie

                          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>
                          


                          My login-error-redirect.jsp:

                          <%-- 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.

                          Ricardo

                          • 10. Re: FormAuthValve
                            Ralph Churchill Newbie

                            Where in your Custom Login module can/should you throw the exception? I see that getRoleSet throws a LoginException, but not validatePassword...

                            RMC

                            • 11. Re: FormAuthValve
                              Ricardo Arguello Newbie

                              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.

                              Ricardo

                              • 12. Re: FormAuthValve
                                joe_the_quick Newbie

                                regarding the window.location-trick:

                                =====================
                                j_exception issue
                                =====================
                                first of all, it's much nicer to use response.sendRedirect() instead of any javascript.

                                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).

                                =====================
                                org.jboss.web.tomcat.security.FormAuthValve
                                =====================
                                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.

                                regards,
                                Johannes



                                • 13. Re: FormAuthValve
                                  Ricardo Arguello Newbie

                                  There is an easier way to acomplish this with JBoss 4.0.3+:

                                  http://wiki.jboss.org/wiki/Wiki.jsp?page=ExtendedFormAuthenticator