Announcement Announcement Module
Collapse
No announcement yet.
Unable to set BASIC authentication header Page Title Module
Move Remove Collapse
This topic is closed
X
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Unable to set BASIC authentication header

    I have a rich client that accesses an Acegi-protected business logic module that is running in Tomcat.

    The remote calls work fine most of the time, but sometimes I get the following: (from the client's log)

    Code:
    2005-02-14 15:54:05,103 DEBUG
    [AuthenticationSimpleHttpInvokerRequestExecutor] Sending HTTP invoker
    request for service at [http://localhost:8081/Grace/Remote/DonorManager],
    with size 376
    2005-02-14 15:54:05,103 DEBUG
    [AuthenticationSimpleHttpInvokerRequestExecutor] Unable to set BASIC
    authentication header as ContextHolder: null; does not provide a
    SecureContext
    Which leads to a: net.sf.acegisecurity.AuthenticationCredentialsNotF oundException:
    Authentication credentials were not found in the SecureContext

    On normal method calls I get:

    Code:
    2005-02-14 15:53:57,728 DEBUG
    [AuthenticationSimpleHttpInvokerRequestExecutor] Sending HTTP invoker
    request for service at [http://localhost:8081/Grace/Remote/DepositManager],
    with size 389
    2005-02-14 15:53:57,728 DEBUG
    [AuthenticationSimpleHttpInvokerRequestExecutor] HttpInvocation now
    presenting via BASIC authentication ContextHolder-derived:
    net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken@1c6572b:
    Username: someuser; Password: [PROTECTED]; Authenticated: false; Details:
    null; Granted Authorities: ROLE_DATAENTRY
    The setup for the two business logic beans are exactly the same on both ends:

    Client:
    Code:
      <!-- Deposit Manager -->
      <bean id="depositManager" 
            class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
        <property name="serviceUrl"><value>$&#123;graceAdmin.moduleUrl&#125;/DepositManager</value></property>
        <property name="serviceInterface"><value>org.dm.daniel.grace.business.DepositManager</value></property>
        <property name="httpInvokerRequestExecutor"><ref local="httpInvokerRequestExecutor"/></property>
      </bean>
    
      <!-- Donor Manager -->
      <bean id="donorManager" 
            class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
        <property name="serviceUrl"><value>$&#123;graceAdmin.moduleUrl&#125;/DonorManager</value></property>
        <property name="serviceInterface"><value>org.dm.daniel.grace.business.DonorManager</value></property>
        <property name="httpInvokerRequestExecutor"><ref local="httpInvokerRequestExecutor"/></property>
      </bean>
    
      <bean id="httpInvokerRequestExecutor" class="net.sf.acegisecurity.ui.httpinvoker.AuthenticationSimpleHttpInvokerRequestExecutor"/>
    Server:
    Code:
      <bean id="depositManager" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces"><value>org.dm.daniel.grace.business.DepositManager</value></property>
        <property name="interceptorNames">
          <list>
            <idref local="securityInterceptor"/>
            <idref local="depositManagerTarget"/>
          </list>
        </property>
      </bean>
    
      <bean id="donorManager" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces"><value>org.dm.daniel.grace.business.DonorManager</value></property>
        <property name="interceptorNames">
          <list>
            <idref local="securityInterceptor"/>
            <idref local="donorManagerTarget"/>
          </list>
        </property>
      </bean>
    
    	<!-- 
    	    Security Interceptor	
    	-->
    	<bean id="securityInterceptor" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
    		<property name="validateConfigAttributes"><value>true</value></property>
    		<property name="authenticationManager"><ref local="authenticationManager"/></property>
    		<property name="accessDecisionManager"><ref local="accessDecisionManager"/></property>
    	    <!--    <property name="runAsManager"><ref local="runAsManager"/></property> -->
    		<property name="objectDefinitionSource">
    			<value>
    		        org.dm.daniel.grace.business.DepositManager.*=ROLE_DATAENTRY
    		        org.dm.daniel.grace.business.DonorManager.*=ROLE_DATAENTRY
    		        </value>
    		</property>
    	</bean>
    Why would the Context holder be available to populate the HTTP BASIC auth header on some method calls and not on others?

    Thanks ahead of time for your thoughts.

  • #2
    It's definitely a client-side problem, and I suspect a threading one within your application. Your ContextHolder.getContext() is returning null, meaning the current thread of execution never had a ContextHolder.setContext(Context) take place. Fortunately Log4J can help you out by seeing the name of the thread performing calls. Use a pattern like this:

    Code:
    log4j.appender.stdout.layout.conversionPattern=&#91;%p,%c&#123;1&#125;,%t&#93; %m%n
    I suspect you will notice the problem matches the executing thread.

    Comment


    • #3
      Ben,
      Thanks for the swift reply. The method call that is problematic is in fact running in another thread. My client app is a standard GUI that is always spawning threads. How do I make it so that every thread in my program has the proper context holder static data set? It seems like I might just need to override
      net.sf.acegisecurity.ui.httpinvoker.Authentication SimpleHttpInvokerRequestExecutor
      to make it so that I get the context holder from some location that is available across threads. How would you handle this?

      Comment


      • #4
        Unfortunately I have no specific suggestions - it's really a client-specific issue and the approach will vary depending on your client threading design, availablility of a shared object holder etc.

        Comment


        • #5
          Originally posted by general_pattonm
          Ben,
          Thanks for the swift reply. The method call that is problematic is in fact running in another thread. My client app is a standard GUI that is always spawning threads. How do I make it so that every thread in my program has the proper context holder static data set? It seems like I might just need to override
          net.sf.acegisecurity.ui.httpinvoker.Authentication SimpleHttpInvokerRequestExecutor
          to make it so that I get the context holder from some location that is available across threads. How would you handle this?
          I have encountered the same issue. How did you end up resolving it?

          Comment


          • #6
            Instead of using the normal Acegi class for adding user's auth data to a remote request (AuthenticationSimpleHttpInvokerRequestExecutor), I created a new class based on it:

            Code:
            /**
             * Adds BASIC authentication support to <code>SimpleHttpInvokerRequestExecutor</code>,
             * but retrieves the current user's authentication data from Spring, not the
             * static ContextHolder, thus making it suitable for multi-threaded apps.
             * <P>
             * Based on <code>AuthenticationSimpleHttpInvokerRequestExecutor</code> by Ben Alex.
             * 
             * @author pattonm
             * @version $Revision&#58; 1.1 $
             */
            public class AuthCrossThreadHttpInvokerRequestExecutor extends SimpleHttpInvokerRequestExecutor &#123;
                /////////////////////////////
                // Normal member variables //
                /////////////////////////////
            
                protected CrossThreadContextHolder contextHolder = null;  
                
                /**
                 * Provided so subclasses can perform additional configuration if required
                 * &#40;eg set additional request headers for non-security related information
                 * etc&#41;.
                 *
                 * @param con the HTTP connection to prepare
                 * @param contentLength the length of the content to send
                 *
                 * @throws IOException if thrown by HttpURLConnection methods
                 */
                protected void doPrepareConnection&#40;HttpURLConnection con, int contentLength&#41; throws IOException &#123;
                &#125;
            
                /**
                 * Called every time a HTTP invocation is made.
                 * 
                 * <P>
                 * Simply allows the parent to setup the connection, and then adds an
                 * <code>Authorization</code> HTTP header property that will be used for
                 * BASIC authentication.
                 * </p>
                 * 
                 * <P>
                 * The spring-managed <code>ContextHolder</code> is used to obtain the 
                 * relevant principal and credentials.
                 * </p>
                 *
                 * @param con the HTTP connection to prepare
                 * @param contentLength the length of the content to send
                 *
                 * @throws IOException if thrown by HttpURLConnection methods
                 * @throws AuthenticationCredentialsNotFoundException if the
                 *         <code>ContextHolder</code> does not contain a valid
                 *         <code>Authentication</code> with both its
                 *         <code>principal</code> and <code>credentials</code> not
                 *         <code>null</code>
                 */
                protected void prepareConnection&#40;HttpURLConnection con, int contentLength&#41;
                    throws IOException, AuthenticationCredentialsNotFoundException &#123;
                    super.prepareConnection&#40;con, contentLength&#41;;
            
                    // Add the basic authorization headers
                    if &#40;contextHolder == null&#41; &#123;
                        if &#40;logger.isDebugEnabled&#40;&#41;&#41; &#123;
                            logger.debug&#40;
                                "Unable to set BASIC authentication header as contextHolder " +
                                "member variable is null."&#41;;
                        &#125;
                    &#125;
                    
                    if &#40;&#40;contextHolder.getContext&#40;&#41; != null&#41;
                        && &#40;contextHolder.getContext&#40;&#41; instanceof SecureContext&#41;&#41; &#123;
                        Authentication auth = &#40;&#40;SecureContext&#41; contextHolder.getContext&#40;&#41;&#41;
                            .getAuthentication&#40;&#41;;
            
                        if &#40;&#40;auth != null&#41; && &#40;auth.getPrincipal&#40;&#41; != null&#41;
                            && &#40;auth.getCredentials&#40;&#41; != null&#41;&#41; &#123;
                            String base64 = auth.getPrincipal&#40;&#41;.toString&#40;&#41; + "&#58;"
                                + auth.getCredentials&#40;&#41;.toString&#40;&#41;;
                            con.setRequestProperty&#40;"Authorization",
                                "Basic "
                                + new String&#40;Base64.encodeBase64&#40;base64.getBytes&#40;&#41;&#41;&#41;&#41;;
            
                            if &#40;logger.isDebugEnabled&#40;&#41;&#41; &#123;
                                logger.debug&#40;
                                    "HttpInvocation now presenting via BASIC authentication ContextHolder-derived&#58; "
                                    + auth.toString&#40;&#41;&#41;;
                            &#125;
                        &#125; 
                        else &#123;
                            if &#40;logger.isDebugEnabled&#40;&#41;&#41; &#123;
                                logger.debug&#40;
                                    "Unable to set BASIC authentication header as ContextHolder&#58; "
                                    + contextHolder.getContext&#40;&#41;
                                    + "; did not provide valid Authentication&#58; " + auth&#41;;
                            &#125;
                        &#125;
                    &#125; 
                    else &#123;
                        if &#40;logger.isDebugEnabled&#40;&#41;&#41; &#123;
                            logger.debug&#40;
                                "Unable to set BASIC authentication header as ContextHolder&#58; "
                                + contextHolder.getContext&#40;&#41;
                                + "; does not provide a SecureContext"&#41;;
                        &#125;
                    &#125;
            
                    doPrepareConnection&#40;con, contentLength&#41;;
                &#125;
                
                public CrossThreadContextHolder getContextHolder&#40;&#41; &#123;
                    return contextHolder;
                &#125;
                public void setContextHolder&#40;CrossThreadContextHolder contextHolder&#41; &#123;
                    this.contextHolder = contextHolder;
                &#125;
            &#125;
            It uses a CrossThreadContextHolder, which, instead of storing the data in a static member variable, uses a normal non-static member variable:

            Code:
            /**
             * Associates a given Context with a member variable, for use across 
             * multiple execution threads.
             * 
             * @author pattonm
             * @version $Revision&#58; 1.2 $
             */
            public class CrossThreadContextHolder &#123;
                private Context context = null;
                
                public CrossThreadContextHolder&#40;&#41; &#123;
                &#125;
                
                public Context getContext&#40;&#41; &#123;
                    return context;
                &#125;
                public void setContext&#40;Context context&#41; &#123;
                    this.context = context;
                &#125;
            &#125;
            Then, I just put both of them in Spring:

            Code:
              <!-- Automatically propagates ContextHolder-managed Authentication principal
                   and credentials to a HTTP invoker BASIC authentication header -->
              <bean id="httpInvokerRequestExecutor" class="org.dm.daniel.grace.ui.util.AuthCrossThreadHttpInvokerRequestExecutor">
                <property name="contextHolder"><ref bean="contextHolder"/></property>
              </bean>
            
              <!-- Bean for holding the secure context which holds the currently logged in
            	   user's authentication principal and credentials -->
              <bean id="contextHolder" class="org.dm.daniel.grace.ui.util.CrossThreadContextHolder"/>
            And after I've authenticated I put the authentication class in the CrossThreadContextHolder:

            Code:
            	    	CrossThreadContextHolder contextHolder = &#40;CrossThreadContextHolder&#41;appCtx.getBean&#40;"contextHolder"&#41;;
            
            	    	SecureContext secureContext = new SecureContextImpl&#40;&#41;;
            	        secureContext.setAuthentication&#40;result&#41;;
            	       	contextHolder.setContext&#40;secureContext&#41;;
            I don't know Acegi too well so their might be problems to this admittedly naive approach, but it works fine for me since both threads can access spring, but both can't access the static data.

            Comment


            • #7
              Your approach looks fine to me. You're setting the BASIC authentication headers on your client, and that's all that really needs to be done from an Acegi Security perspective.

              Comment

              Working...
              X