1 2 Previous Next 15 Replies Latest reply on Feb 4, 2008 4:05 PM by Alan Feng

    Exception with basic authentication

    Chris Lowe Apprentice

      If I try to access a page that has basic HTTP authentication in place, I get the following exception:

      08:47:28,131 ERROR [ExceptionFilter] handling uncaught exception
      java.lang.NullPointerException
       at org.jboss.seam.web.AuthenticationFilter.processBasicAuth(AuthenticationFilter.java:158)
       at org.jboss.seam.web.AuthenticationFilter.doFilter(AuthenticationFilter.java:118)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.jboss.seam.web.MultipartFilter.doFilter(MultipartFilter.java:85)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:44)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:141)
       at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:281)
       at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:60)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
       at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
       at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
       at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
       at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
       at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:432)
       at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
       at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
       at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
       at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
       at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
       at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
       at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
       at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
       at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
       at java.lang.Thread.run(Thread.java:595)


      I'm using Seam 2.0.0.GA, JBossAS 4.2.2.GA, JBossWS 2.0.2.GA

      To recreate:

      I created a Seam project in RHDS RC1. Under WebContent, I created a folder called "auth". Then I added the following page under auth, called "authenticate.xhtml",

      <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <ui:composition xmlns="http://www.w3.org/1999/xhtml"
       xmlns:ui="http://java.sun.com/jsf/facelets">
      
      <ui:define name="body">
      Authenticate
      </ui:define>
      </ui:composition>


      I added the following config to components.xml

      <components ...
       xmlns:web="http://jboss.com/products/seam/web"
       xsi:schemaLocation=
       "...
       http://jboss.com/products/seam/web http://jboss.com/products/seam/web-2.0.xsd">
      
      ...
      
       <web:authentication-filter url-pattern="/auth/*" auth-type="basic"/>
      
      </components>


      If I deploy to JBossAS and access the page, I get the exception.


      I'm actually looking to use basic authentication to lock down some web services as described here:

      http://jbws.dyndns.org/mediawiki/index.php?title=Authentication

      Is this possible using Seam web services?

      Best Regards,

      Chris.

        • 1. Re: Exception with basic authentication
          Chris Lowe Apprentice

          I had a look at the code for AuthenticationFilter.processBasicAuth(). It actually looks like Identity is not being found in the context:

          private void processBasicAuth(HttpServletRequest request,
           HttpServletResponse response, FilterChain chain)
           throws IOException, ServletException
           {
           Context ctx = new SessionContext( new ServletRequestSessionMap(request) );
           Identity identity = (Identity) ctx.get(Identity.class);
          
           // identity is null
           }



          For what it's worth, here is the HTTP request:

          GET /MisuraServices/auth/authenticate.seam HTTP/1.0
          Host: localhost:8080
          User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.10) Gecko/20071115 Firefox/2.0.0.10
          Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          Accept-Language: en-us,en;q=0.5
          Accept-Encoding: gzip,deflate
          Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          Keep-Alive: 300
          Cookie: sys_testCookie=True; JSESSIONID=65C3BE650E56D7F8506901B811CC6B97


          Cheers,

          C.

          • 2. It's a bug
            Alan Feng Novice

            I had the same problem and found out that the BASIC auth code is pretty much borken in Seam 2.0GA (not sure about early versions).

            [The Problems]
            The code that causing the problem is in AuthenticationFilter.processBasicAuth(). The current code looks like:

             private void processBasicAuth(HttpServletRequest request,
             HttpServletResponse response, FilterChain chain)
             throws IOException, ServletException
             {
            Problem 1
             Context ctx = new SessionContext( new ServletRequestSessionMap(request) );
             Identity identity = (Identity) ctx.get(Identity.class);
            
             boolean requireAuth = false;
            
             String header = request.getHeader("Authorization");
             if (header != null && header.startsWith("Basic "))
             {
             String base64Token = header.substring(6);
             String token = new String(Base64.decode(base64Token));
            
             String username = "";
             String password = "";
             int delim = token.indexOf(":");
            
             if (delim != -1)
             {
             username = token.substring(0, delim);
             password = token.substring(delim + 1);
             }
            
             // Only reauthenticate if username doesn't match Identity.username and user isn't authenticated
            Problem 2
             if (!username.equals(identity.getUsername()) || !identity.isLoggedIn())
             {
             identity.setUsername(username);
             identity.setPassword(password);
             }
            
             }
            
             if (!identity.isLoggedIn() && !identity.isCredentialsSet())
             {
             requireAuth = true;
             }
            
             try
             {
             if (!requireAuth)
             {
             chain.doFilter(request, response);
             return;
             }
             }
             catch (NotLoggedInException ex)
             {
             requireAuth = true;
             }
            
             if (requireAuth && !identity.isLoggedIn())
             {
             response.addHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Not authorized");
             }
             }
            


            As shown in the above code with red title and bold texts, two problems exist.
            Problem 1:
            The Identity object is fetched from a brand new SessionContext which returns null if it is the first time the user access a site and the page accessed is protected by BASIC auth. This results in the NPE mentioned in the previous posts, which also has the effect of, if you have the seam-debug on, remembering this exception and redirect to the debug page after you are properly authenticated.
            Problem 2
            The username and password are parsed correctly and assigned to the identity object, however, it never calls the identity.authenticate() to actually perform the authentication!!

            [The workaround]
            Create a application component with the same name to fix the logic. Two changes are required:
            Change components.xml file
            Make sure your components.xml contains a line like this:
             <web:authentication-filter url-pattern="*.seam" auth-type="basic" realm="My App" precedence="0"/>
            

            What is important is the precedence="0" attribute. It makes sure that the component is initialized with the BUILT_IN precedence.

            Create your own substitute component to fix the problem
            Using the code below, deploy with your app to shadow the built in AuthenticationFilter component:
            package com.mycompany.myapp.util;
            
            import static org.jboss.seam.ScopeType.APPLICATION;
            
            import java.io.IOException;
            
            import javax.security.auth.login.LoginException;
            import javax.servlet.FilterChain;
            import javax.servlet.ServletException;
            import javax.servlet.ServletRequest;
            import javax.servlet.ServletResponse;
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;
            
            import org.jboss.seam.Seam;
            import org.jboss.seam.annotations.Install;
            import org.jboss.seam.annotations.Logger;
            import org.jboss.seam.annotations.Name;
            import org.jboss.seam.annotations.Scope;
            import org.jboss.seam.annotations.intercept.BypassInterceptors;
            import org.jboss.seam.annotations.web.Filter;
            import org.jboss.seam.log.Log;
            import org.jboss.seam.security.Identity;
            import org.jboss.seam.security.NotLoggedInException;
            import org.jboss.seam.servlet.ContextualHttpServletRequest;
            import org.jboss.seam.util.Base64;
            import org.jboss.seam.web.AuthenticationFilter;
            
            /**
             * Fix bug in the Seam AuthenticationFilter when handling the BASIC HTTP authentication.
             *
             * Overwrites the BUILT_IN component with the same name.
             *
             * @author Alan Feng
             */
            
            @Scope(APPLICATION)
             @Name("org.jboss.seam.web.authenticationFilter")
             @Install(precedence = Install.APPLICATION)
             @BypassInterceptors
             @Filter(within = "org.jboss.seam.web.exceptionFilter")
            
            public class AuthenticaitonFilterFix extends AuthenticationFilter {
            
             private static final String AUTH_TYPE_BASIC = "basic";
            
             @Logger
             private Log log;
            
             @Override
             public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
             ServletException {
             if (!(request instanceof HttpServletRequest)) {
             throw new ServletException("This filter can only process HttpServletRequest requests");
             }
            
             HttpServletRequest httpRequest = (HttpServletRequest) request;
             HttpServletResponse httpResponse = (HttpServletResponse) response;
            
             if (AUTH_TYPE_BASIC.equals(getAuthType()))
             processBasicAuthFix(httpRequest, httpResponse, chain); // invoke the fix
             else
             super.doFilter(request, response, chain);
             }
            
             /**
             * Fixes the bug that does not resolve the Identity object properly, which causes the NPE.
             *
             * @param request
             * @param response
             * @param chain
             * @throws IOException
             * @throws ServletException
             */
             private void processBasicAuthFix(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
             throws IOException, ServletException {
             final Identity identity = (Identity) request.getSession().getAttribute(Seam.getComponentName(Identity.class));
            
             boolean requireAuth = false;
            
             String header = request.getHeader("Authorization");
             if (header != null && header.startsWith("Basic ")) {
             String base64Token = header.substring(6);
             String token = new String(Base64.decode(base64Token));
            
             String username = "";
             String password = "";
             int delim = token.indexOf(":");
            
             if (delim != -1) {
             username = token.substring(0, delim);
             password = token.substring(delim + 1);
             }
            
             // Only reauthenticate if username doesn't match Identity.username and user isn't
             // authenticated
             if (!username.equals(identity.getUsername()) || !identity.isLoggedIn()) {
             identity.setUsername(username);
             identity.setPassword(password);
            
             // HERE we are invoking the authentication, which does JAAS login
             try {
             new ContextualHttpServletRequest(request) {
             @Override
             public void process() throws ServletException, IOException, LoginException {
             identity.authenticate();
             }
             }.run();
             } catch (Exception ex) {
             log.error("Error authenticating: " + ex.getMessage());
             requireAuth = true;
             }
            
             }
             }
            
             if (!identity.isLoggedIn() && !identity.isCredentialsSet()) {
             requireAuth = true;
             }
            
             try {
             if (!requireAuth) {
             chain.doFilter(request, response);
             return;
             }
             } catch (NotLoggedInException ex) {
             requireAuth = true;
             }
            
             if (requireAuth && !identity.isLoggedIn()) {
             response.addHeader("WWW-Authenticate", "Basic realm=\"" + getRealm() + "\"");
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Not authorized");
             }
             }
            }
            


            Important changes are in bold texts above.

            I will create a JIRA issue shortly.


            • 4. Re: Exception with basic authentication
              Shane Bryzak Master

              This should be fixed in CVS now, please let me know if there's any further issues with HTTP authentication.

              • 5. Re: Exception with basic authentication
                Alan Feng Novice

                 

                "shane.bryzak@jboss.com" wrote:
                This should be fixed in CVS now, please let me know if there's any further issues with HTTP authentication.


                Has this been tested? Because when I look at the code in the latest trunk, I didn't see any code that would fix the second problem I mentioned (see the "Problem 2" in my previous posts). That is, the basic auth processing logics does not call identity.authenticate() anywhere. Where is the authentication been triggered?

                • 6. Re: Exception with basic authentication
                  Shane Bryzak Master

                  The authentication should be triggered when calling/accessing a restricted Seam component, doing the chain.doFilter() call. It can't happen in the filter as not all contexts exist at that point in time.

                  • 7. Re: Exception with basic authentication
                    Alan Feng Novice

                     

                    "shane.bryzak@jboss.com" wrote:
                    The authentication should be triggered when calling/accessing a restricted Seam component, doing the chain.doFilter() call. It can't happen in the filter as not all contexts exist at that point in time.


                    My point might not be right as I am not as familiar to the framework as you do. However, I don't think your answer is correct.

                    chain.doFilter() will trigger the AuthenticationFilter logics to challenge the client for entering username / password. Once that information is entered, browser sends it to the server in the HTTP header. Seam then needs to invoke the #{authenticator.authenticate} method to perform the authentication in order to validate the username / password, which is defined in the components.xml file:
                    <security:identity authenticate-method="#{authenticator.authenticate}" />


                    The current logics only obtains the username / password, but does not invoke the authenticate method to actually validate this information. If you look at the
                    processDigestAuth() method of the AuthenticationFilter class, you will see it actually invokes the authenticate() method:
                     private void authenticate(HttpServletRequest request, final String username)
                     throws ServletException, IOException
                     {
                     new ContextualHttpServletRequest(request)
                     {
                     @Override
                     public void process() throws ServletException, IOException, LoginException
                     {
                     Identity identity = Identity.instance();
                     identity.setUsername(username);
                     identity.authenticate();
                     }
                     }.run();
                     }
                    


                    As the code shows, this invocation is done in the ContextualHttpServletRequest environment, which creates necessary context objects. processBasicAuth() needs to do the same thing.

                    • 8. Re: Exception with basic authentication
                      Shane Bryzak Master

                      If you're invoking a restricted method and the credentials are set (which is what the AuthenticationFilter does) then a silent login will automatically occur, with no need to explicitly call Identity.authenticate(). Digest authentication is a special case, with a special type of authenticator. You have to keep in mind that the request may be for an unsecured resource, for which authentication may not be required. If that is the case then you don't want to be prompting the user for their username and password.

                      • 9. Re: Exception with basic authentication
                        Alan Feng Novice

                         

                        "shane.bryzak@jboss.com" wrote:
                        If you're invoking a restricted method and the credentials are set (which is what the AuthenticationFilter does) then a silent login will automatically occur, with no need to explicitly call Identity.authenticate(). Digest authentication is a special case, with a special type of authenticator. You have to keep in mind that the request may be for an unsecured resource, for which authentication may not be required. If that is the case then you don't want to be prompting the user for their username and password.


                        I wasn't able to find any code to support your claim so far.

                        First of all, the AuthenticationFilter is only invoked when its urlPattern matches the requested resource. This is done in the SeamFilter$FilterChainImpl inner class:
                         if (filter instanceof AbstractFilter)
                         {
                         AbstractFilter bf = (AbstractFilter) filter;
                        
                         if ( bf.isMappedToCurrentRequestPath(request) )
                         {
                         filter.doFilter(request, response, this);
                         }
                        
                         else
                         {
                         this.doFilter(request, response);
                         }
                         }
                        
                        

                        Therefore, it will not be used if the resource requested is not a protected one.

                        Secondly, I'd like to see you pointing out to me where the "silent login" happens. So far with my own research, the JAAS login() is triggered via the Identity.authenticate() call. This is not a automatic process, it needs to be invoked from somewhere and it is not done with the current code if it is BASIC authentication.

                        I have to question that if you have done any testing on this, or even look it up in your code before making your claim. I have put in the identity fix myself and tried it out before posting my workaround, and my test confirmed that without the 2nd fix I mentioned, the authentication does not "silently" happen. You end up keep getting username/password prompt from the client browser.



                        • 10. Re: Exception with basic authentication
                          Shane Bryzak Master

                          When I say protected, I mean that either:

                          a) login-required="true" is set in pages.xml
                          b) A element is specified in pages.xml
                          or
                          c) A component method is annotated with @Restrict

                          As for the silent authentication, look at Identity.hasRole() and Identity.checkPermission(), both of these methods call isLoggedIn(true), which will attempt to authenticate if the user's credentials are set.

                          I have tested this code and it all works as expected. If you are experiencing problems then perhaps there is something particular to your use case that I am missing. If you would like to raise an issue in JIRA with a reproducible test case then I'd be happy to look at it for you.

                          • 11. Re: Exception with basic authentication
                            Shane Bryzak Master

                            Bah, my xml-tags got stripped out of my response. Replace b) with:

                            b) A <restrict> element is specified in pages.xml


                            • 12. Re: Exception with basic authentication
                              Mihai Lazar Newbie

                              Try this :D STOP USING RHDS RC1. I had allot of hassle with that thing to.

                              Download Eclipse WTP and JBOss TOOLS.. good luck

                              • 13. Re: Exception with basic authentication
                                Alan Feng Novice

                                I think the use case is slightly different. The way I enabled the HTTP basic auth is based on the reference doc by editing the components.xml file:

                                 <web:authentication-filter url-pattern="*.seam" auth-type="basic" realm="My App"/>
                                


                                All pages are protected already with the url-pattern attribute and there is no additional changes to the pages.xml file or annotations to the class, such as "restricted".

                                I swapped in your changes to the application and tested again, and it is still broken. I see what is missing from your testing. In the components.xml file, I specified:
                                 <security:identity authenticate-method="#{myAuthenticator.authenticate}" />
                                

                                So the "muAuthenticator.authenticate() should be invoked when you validate the username/password. But with your fix, it flagged the indentity component as logged in without invoking my authentication method.

                                What this mean is that my authentication logics is not used at all, and the user can enter any random string as username/password to get in the protected area.

                                The only thing different from what I described before is that Seam accepts any username / password and doesn't repeatedly prompt the browser for the username / password.

                                Another thing worth mentioning is that the hasRole() may never get called as my app does not use the "role" to authorize access yet.

                                Did I make myself clear? Please specify a "authenticate-method" and see if it is triggered in your test.


                                • 14. Re: Exception with basic authentication
                                  Shane Bryzak Master

                                  Ok, there's obviously a misunderstanding as to what is considered to be a protected page. Rather than drag this out, I've modified the filter to authenticate every request, regardless of whether there's any security restrictions on the page or not. Hopefully this should now work for you.

                                  1 2 Previous Next