[jboss-user] [JBoss Seam] - It's a bug

alllle do-not-reply at jboss.com
Wed Jan 30 15:07:44 EST 2008


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.


View the original post : http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4124922#4124922

Reply to the post : http://www.jboss.com/index.html?module=bb&op=posting&mode=reply&p=4124922



More information about the jboss-user mailing list