Announcement Announcement Module
Collapse
No announcement yet.
Question regarding BasicProcessingFilter Page Title Module
Move Remove Collapse
This topic is closed
X
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Question regarding BasicProcessingFilter

    I have a need for http basic authentication for one of my controllers (it generates a rss feed and basic authentication is the only commonly supported option). The BasicProcessingFilter seems to assume that the user agent will always supply the "Authorization" header without prompting. However, I believe that user agents typically will not supply the "Authorization" header unless prompted by a "HTTP/1.x 401 Unauthorized" response to a request. The code as written only provides this response if it is unable to process the provided authorization header data.

    This seems a pretty typical flow (request, challenge, response) in a web application, so I'm wondering if there is a rationale for not supporting it. It seems as simple as calling
    Code:
    authenticationEntryPoint.commence(request, response)
    if the authentication header doesn't exist just as is the case if authentication fails, but perhaps there is a complication I'm not taking into account.

    Please enlighten me...

    Thanks,

    Rob

  • #2
    After reading http://forum.springframework.org/showthread.php?t=9937, I came to the conclusion that the problem is that the SecurityEnforcementFilter is pointing to a AuthenticationProcessingFilterEntryPoint rather than independently defined BasicProcessingFilterEntryPoint. I really want to support both models -- usually I want the AuthenticationProcessingFilterEntryPoint or form-based login -- so I'm curious if it's possible to have two SecurityEnforcementFilters defined. I tried it and it works for the controller that needs Basic authentication, but not for the ones that use form-based authentication. It looks like the user is not set up in session but at the same time I'm not presented with a form to log in so I suspect that something is amiss.

    So does anybody know if this approach is supported?

    Thanks,

    Rob
    Last edited by robyn; May 19th, 2006, 06:44 AM.

    Comment


    • #3
      You could make two separate SecurityEnforcementFilter instances with different AuthenticationEntryPoints. One could map (at a web.xml filter mapping level) to your RSS URIs, and the other to the remainder of your webapp.

      Alternatively, you could write a new AuthenticationEntryPoint that proxies the AuthenticationProcessingFilterEntryPoint and BasicProcessingFilterEntryPoint. It woudl determine which to delegate to based on the ServletRequest passed as an argument to the commence(request, response) method.

      Comment


      • #4
        Thanks, Ben.

        I tried using two separate SecurityEnforcementFilters but the second one -- using the basic auth entry point -- doesn't seem to work. Looking at the log, it looks like the filter interceptor is never getting triggered.

        Perhaps my configuration is incorrect, so please find the relevant parts below:

        web.xml:
        Code:
        <filter>
                <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name>
                <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
                <init-param>
                    <param-name>targetClass</param-name>
                   <param-value>net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter</param-value>
                </init-param>
            </filter>
            <filter-mapping>
                <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name>
                <url-pattern>*.rss</url-pattern>
            </filter-mapping>
            <servlet-mapping>
                <servlet-name>spring</servlet-name>
                <url-pattern>*.rss</url-pattern>
            </servlet-mapping>
        applicationContext.xml:
        Code:
        <bean id="rssFilterSecurityInterceptor"
                class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor">
                <property name="authenticationManager">
                    <ref bean="authenticationManager"/>
                </property>
                <property name="accessDecisionManager">
                    <ref bean="accessDecisionManager"/>
                </property>
                <property name="objectDefinitionSource">
                    <value><!&#91;CDATA&#91;
        				CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
        			    PATTERN_TYPE_APACHE_ANT
        			    /feeds/*.rss=ROLE_USER
        				&#93;&#93;></value>
                </property>
            </bean>
            <bean id="basicSecurityEnforcementFilter"
                class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
                <property name="filterSecurityInterceptor">
                    <ref bean="rssFilterSecurityInterceptor"/>
                </property>
                <property name="authenticationEntryPoint">
                    <ref bean="basicAuthenticationEntryPoint"/>
                </property>
            </bean>
            <bean id="basicProcessingFilter"
                class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
                <property name="authenticationManager">
                    <ref bean="authenticationManager"/>
                </property>
                <property name="authenticationEntryPoint">
                    <ref bean="basicAuthenticationEntryPoint"/>
                </property>
            </bean>
            <bean id="basicAuthenticationEntryPoint"
                class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
                <property name="realmName">
                    <value>My Domain</value>
                </property>
            </bean>
        And my log file:
        Code:
        2005-01-27 16&#58;04&#58;24,906 DEBUG &#91;net.sf.acegisecurity.ui.AbstractIntegrationFilter&#93; - <Authentication added to ContextHolder from container>
        2005-01-27 16&#58;04&#58;24,908 DEBUG &#91;net.sf.acegisecurity.intercept.web.PathBasedFilterInvocationDefinitionMap&#93; - <Converted URL to lowercase, from&#58; 'org.apache.catalina.connector.RequestFacade@1ed957d'; to&#58; '/feeds/alerts.rss'>
        2005-01-27 16&#58;04&#58;24,908 DEBUG &#91;net.sf.acegisecurity.intercept.web.PathBasedFilterInvocationDefinitionMap&#93; - <Candidate is&#58; '/feeds/alerts.rss'; pattern is /app/**; matched=false>
        2005-01-27 16&#58;04&#58;24,908 DEBUG &#91;net.sf.acegisecurity.intercept.web.PathBasedFilterInvocationDefinitionMap&#93; - <Candidate is&#58; '/feeds/alerts.rss'; pattern is /admin/**; matched=false>
        2005-01-27 16&#58;04&#58;24,908 DEBUG &#91;net.sf.acegisecurity.intercept.web.PathBasedFilterInvocationDefinitionMap&#93; - <Candidate is&#58; '/feeds/alerts.rss'; pattern is /servlet/**; matched=false>
        2005-01-27 16&#58;04&#58;24,909 DEBUG &#91;net.sf.acegisecurity.intercept.web.PathBasedFilterInvocationDefinitionMap&#93; - <Candidate is&#58; '/feeds/alerts.rss'; pattern is /errors/**; matched=false>
        2005-01-27 16&#58;04&#58;24,909 DEBUG &#91;net.sf.acegisecurity.intercept.AbstractSecurityInterceptor&#93; - <Public object - authentication not attempted>
        2005-01-27 16&#58;04&#58;24,909 DEBUG &#91;net.sf.acegisecurity.intercept.AbstractSecurityInterceptor&#93; - <Authentication object detected and tagged as unauthenticated>
        2005-01-27 16&#58;04&#58;24,909 DEBUG &#91;net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter&#93; - <Authorization header&#58; null

        Comment


        • #5
          Looks OK to me, but where is your filter mapping that aims to load basicSecurityEnforcementFilter? It needs to only fire basicSecurityEnforcementFilter when *.rss is detected. Similarly, the other SecurityEnforcementFilter shouldn't get fired unless the path is not *.rss.

          Thinking a bit more about the application context and web.xml duplication this approach would cause, I think it would be more elegant to use the delegating AuthenticationEntryPoint, just checking the request URL for whether it ends in rss and delegating accordingly. I just whipped one up for you (but didn't test it):

          Code:
          /* Copyright 2004 Acegi Technology Pty Limited
           *
           * Licensed under the Apache License, Version 2.0 &#40;the "License"&#41;;
           * you may not use this file except in compliance with the License.
           * You may obtain a copy of the License at
           *
           *     http&#58;//www.apache.org/licenses/LICENSE-2.0
           *
           * Unless required by applicable law or agreed to in writing, software
           * distributed under the License is distributed on an "AS IS" BASIS,
           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
           * See the License for the specific language governing permissions and
           * limitations under the License.
           */
          
          package net.sf.acegisecurity.intercept.web;
          
          import net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint;
          import net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint;
          
          import java.io.IOException;
          
          import javax.servlet.ServletException;
          import javax.servlet.ServletRequest;
          import javax.servlet.ServletResponse;
          import javax.servlet.http.HttpServletRequest;
          
          
          /**
           * Demonstrates how to write a "delegating" entry point that dynamically
           * adjusts itself based on the URL.
           * 
           * @author Ben Alex
           */
          public class DelegatingAuthenticationEntryPoint
              implements AuthenticationEntryPoint &#123;
              //~ Instance fields ========================================================
          
              AuthenticationEntryPoint rssEntryPoint = new BasicProcessingFilterEntryPoint&#40;&#41;;
              AuthenticationEntryPoint webuserEntryPoint = new AuthenticationProcessingFilterEntryPoint&#40;&#41;;
          
              //~ Methods ================================================================
          
              public void commence&#40;ServletRequest request, ServletResponse response&#41;
                  throws IOException, ServletException &#123;
                  if &#40;!&#40;request instanceof HttpServletRequest&#41;&#41; &#123;
                      throw new ServletException&#40;"only supports HttpServletRequest"&#41;;
                  &#125;
          
                  HttpServletRequest httpRequest = &#40;HttpServletRequest&#41; request;
          
                  if &#40;httpRequest.getServletPath&#40;&#41;.endsWith&#40;".rss"&#41;&#41; &#123;
                      rssEntryPoint.commence&#40;request, response&#41;;
                  &#125; else &#123;
                      webuserEntryPoint.commence&#40;request, response&#41;;
                  &#125;
              &#125;
          &#125;

          Comment


          • #6
            Thanks for the code, Ben.

            Regarding your question about the filter mapping -- the *.rss mapping was provided in the snippet I posted before. Here is the corresponding bean definitions for the non-rss URLs:

            Code:
             <bean id="filterInvocationInterceptor"
                    class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor">
                    <property name="authenticationManager">
                        <ref bean="authenticationManager"/>
                    </property>
                    <property name="accessDecisionManager">
                        <ref bean="accessDecisionManager"/>
                    </property>
                    <property name="objectDefinitionSource">
                        <value><!&#91;CDATA&#91;
            			CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
            			    PATTERN_TYPE_APACHE_ANT
            				/app/**=ROLE_USER
            				/admin/**=ROLE_PRI_ADMIN
            				/servlet/**=ROLE_USER
            				/errors/**=ROLE_USER
            				&#93;&#93;></value>
                    </property>
                </bean>
                <bean id="authenticationProcessingFilter"
            class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
                    <property name="authenticationManager">
                        <ref bean="authenticationManager"/>
                    </property>
                    <property name="authenticationFailureUrl">
                        <value>/login/login.htm?login_error=1</value>
                    </property>
                    <property name="defaultTargetUrl">
                        <value>/</value>
                    </property>
                    <property name="filterProcessesUrl">
                        <value>/login/j_acegi_security_check</value>
                    </property>
                </bean>
                <bean id="securityEnforcementFilter"
                    class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
                    <property name="filterSecurityInterceptor">
                        <ref bean="filterInvocationInterceptor"/>
                    </property>
                    <property name="authenticationEntryPoint">
                        <ref bean="authenticationProcessingFilterEntryPoint"/>
                    </property>
                </bean>
                <bean id="authenticationProcessingFilterEntryPoint"
            class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
                    <property name="loginFormUrl">
                        <value>/login/login.htm</value>
                    </property>
                    <property name="forceHttps">
                        <value>false</value>
                    </property>
                </bean>
            With regards to the delegation approach -- I don't think it really saves anything in terms of configuration (if I understand correctly) because I still have to define the authentication entry points in the applicationContext.xml (I altered your code and made the entry points properties so I could configure them in the bean definition -- for example, setting the realmName property for the BasicProcessingFilterEntryPoint).

            I'd prefer to use the dual definition approach. I think it would work if I could get the second filter to process the request. It appears as if the first filter correctly decides it isn't interested in the *.rss URL but the second filter is never asked (from what I can see in the logs). Is there something I need to do to chain the filters perhaps?

            Thanks,

            Rob

            Comment


            • #7
              The only Acegi Security filter that will only execute once per request is AbstractIntegrationFilter.

              I am uncertain why the second SecurityEnforcementFilter is never being called. I can only guess it's a web.xml configuration issue. Would you mind posting your entire web.xml, so I can see the order of and configuration of both the Filters themselves as well as their mappings?

              Comment


              • #8
                Thanks, Ben. Please find the web.xml below:

                Code:
                <?xml version="1.0"?>
                <web-app xmlns="http&#58;//java.sun.com/xml/ns/j2ee"
                    xmlns&#58;xsi="http&#58;//www.w3.org/2001/XMLSchema-instance"
                    xsi&#58;schemaLocation="http&#58;//java.sun.com/xml/ns/j2ee http&#58;//java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
                    version="2.4">
                    <display-name>Lorem</display-name>
                    <description>Lorem ipsem UI</description>
                    <filter>
                        <filter-name>Acegi Authentication Processing Filter</filter-name>
                        <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
                        <init-param>
                            <param-name>targetClass</param-name>
                            <param-value>net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter</param-value>
                        </init-param>
                    </filter>
                    <filter>
                        <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name>
                        <filter-class>net.sf.acegisecurity.ui.AutoIntegrationFilter</filter-class>
                    </filter>
                    <filter>
                        <filter-name>Acegi HTTP Request Security Filter</filter-name>
                        <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
                        <init-param>
                            <param-name>targetClass</param-name>
                            <param-value>net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter</param-value>
                        </init-param>
                    </filter>
                    <filter-mapping>
                        <filter-name>Acegi Authentication Processing Filter</filter-name>
                        <url-pattern>/*</url-pattern>
                    </filter-mapping>
                    <filter-mapping>
                        <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name>
                        <url-pattern>/*</url-pattern>
                    </filter-mapping>
                    <filter-mapping>
                        <filter-name>Acegi HTTP Request Security Filter</filter-name>
                        <url-pattern>/*</url-pattern>
                    </filter-mapping>
                    <context-param>
                        <param-name>contextConfigLocation</param-name>
                        <param-value>/WEB-INF/applicationContext.xml,/WEB-INF/applicationContext-datasource.xml,/WEB-INF/applicationContext-hibernate.xml,/WEB-INF/applicationContext-tracker.xml,/WEB-INF/applicationContext-director.xml,/WEB-INF/applicationContext-security.xml,/WEB-INF/applicationContext-scheduler.xml,/WEB-INF/applicationContext-page.xml,/WEB-INF/applicationContext-controller.xml,/WEB-INF/applicationContext-cache.xml</param-value>
                    </context-param>
                    <context-param>
                        <param-name>log4jConfigLocation</param-name>
                        <param-value>/WEB-INF/classes/log4j.xml</param-value>
                    </context-param>
                    <!-- This value MUST match the value in applicationContext-directory.xml for director bean property "contextRootKey" -->
                    <context-param>
                        <param-name>webAppRootKey</param-name>
                        <param-value>lorem.root</param-value>
                    </context-param>
                    <context-param>
                        <param-name>defaultHtmlEscape</param-name>
                        <param-value>true</param-value>
                    </context-param>
                    <listener>
                        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
                    </listener>
                    <listener>
                        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
                    </listener>
                    <servlet>
                        <servlet-name>lorem</servlet-name>
                        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
                        <load-on-startup>1</load-on-startup>
                    </servlet>
                    <servlet>
                        <servlet-name>DisplayChart</servlet-name>
                        <servlet-class>org.jfree.chart.servlet.DisplayChart</servlet-class>
                    </servlet>
                    <servlet-mapping>
                        <servlet-name>lorem</servlet-name>
                        <url-pattern>*.htm</url-pattern>
                    </servlet-mapping>
                    <servlet-mapping>
                        <servlet-name>lorem</servlet-name>
                        <url-pattern>*.xls</url-pattern>
                    </servlet-mapping>
                    <servlet-mapping>
                        <servlet-name>lorem</servlet-name>
                        <url-pattern>*.pdf</url-pattern>
                    </servlet-mapping>
                    <servlet-mapping>
                        <servlet-name>lorem</servlet-name>
                        <url-pattern>*.jsx</url-pattern>
                    </servlet-mapping>
                    <servlet-mapping>
                        <servlet-name>DisplayChart</servlet-name>
                        <url-pattern>/servlet/DisplayChart</url-pattern>
                    </servlet-mapping>
                    <mime-mapping>
                        <extension>ico</extension>
                        <mime-type>image/x-icon</mime-type>
                    </mime-mapping>
                    <mime-mapping>
                        <extension>jsx</extension>
                        <mime-type>text/javascript</mime-type>
                    </mime-mapping>
                    <mime-mapping>
                        <extension>ser</extension>
                        <mime-type>application/java-serialized-object</mime-type>
                    </mime-mapping>
                    <welcome-file-list>
                        <welcome-file>index.jsp</welcome-file>
                    </welcome-file-list>
                    <!-- Rss -->
                    <filter>
                        <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name>
                        <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
                        <init-param>
                            <param-name>targetClass</param-name>
                            <param-value>net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter</param-value>
                        </init-param>
                    </filter>
                    <filter-mapping>
                        <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name>
                        <url-pattern>*.rss</url-pattern>
                    </filter-mapping>
                    <servlet-mapping>
                        <servlet-name>lorem</servlet-name>
                        <url-pattern>*.rss</url-pattern>
                    </servlet-mapping>
                    <!-- AXIS WS DEFINITION -->
                    <servlet>
                        <servlet-name>axis</servlet-name>
                        <servlet-class> org.apache.axis.transport.http.AxisServlet
                        </servlet-class>
                    </servlet>
                    <servlet-mapping>
                        <servlet-name>axis</servlet-name>
                        <url-pattern>/ws/*</url-pattern>
                    </servlet-mapping>
                    <mime-mapping>
                        <extension>wsdl</extension>
                        <mime-type>text/xml</mime-type>
                    </mime-mapping>
                    <mime-mapping>
                        <extension>xsd</extension>
                        <mime-type>text/xml</mime-type>
                    </mime-mapping>
                </web-app>

                Comment


                • #9
                  Replace:

                  Code:
                      <filter>
                          <filter-name>Acegi HTTP Request Security Filter</filter-name>
                          <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
                          <init-param>
                              <param-name>targetClass</param-name>
                              <param-value>net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter</param-value>
                          </init-param>
                      </filter>
                  with

                  Code:
                      <filter>
                          <filter-name>Acegi HTTP Request Security Filter Number 1</filter-name>
                          <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
                          <init-param>
                              <param-name>targetBean</param-name>
                              <param-value>basicSecurityEnforcementFitler</param-value>
                          </init-param>
                      </filter>
                      <filter>
                          <filter-name>Acegi HTTP Request Security Filter</filter-name>
                          <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy Number 2</filter-class>
                          <init-param>
                              <param-name>targetBean</param-name>
                              <param-value>securityEnforcementFitler</param-value>
                          </init-param>
                      </filter>
                  You need to explicitly specifiy the two instances to call.

                  Just curious, what did you find wrong with the delegation approach? It would only mean you need one SecurityEnforcementFilter in web.xml and applicationContext.xml, and your "controller" logic that determines the appropriate entry point is within an obvious and configurable Java class. You'd also have only one location to setup your Ant or Regular Expression paths with secure URLs, meaning it's more flexible in the future.

                  Just thinking some more about the model, we probably could probably put inside the AuthenticationException the ConfigAttributeDefinition that caused the failure. As such, controller logic could be triggered on the presence of a declarative configuration attribute, such as COMMENCE_BASIC...

                  Comment


                  • #10
                    Sorry to return to this so late. I just returned to this issue after being called away on something else and I have upgraded to 0.8.0. I'm trying to use the delegation approach that was put forward by Ben as well as the new filter chain proxy feature in 0.8.0 and have been running into an issue. The authentication processing filter entry point results in a NPE. It looks like the issue is that the authException parameter in the commence method is null. I'm unsure where this should be populated. Anybody have an idea?

                    Thanks,

                    Rob

                    Comment


                    • #11
                      That's a bit odd, as we only added AuthenticationException (authException) to the AuthenticationEntryPoint signature for release 0.8.0:

                      Code:
                          public void commence&#40;ServletRequest request, ServletResponse response,
                              AuthenticationException authException&#41;
                              throws IOException, ServletException;
                      It is most often called by the SecurityEnforcementFilter.sendStartAuthentication( FilterInvocation, AuthenticationException) method. I'd suggest you look at the stack trace for clues.

                      Comment


                      • #12
                        Hi, Ben,

                        The auth exception object is referenced in the last line of the commence method:

                        Code:
                        public void commence&#40;ServletRequest request, ServletResponse response,
                                AuthenticationException authException&#41;
                                throws IOException, ServletException &#123;
                                HttpServletResponse httpResponse = &#40;HttpServletResponse&#41; response;
                                httpResponse.addHeader&#40;"WWW-Authenticate",
                                    "Basic realm=\"" + realmName + "\""&#41;;
                                httpResponse.sendError&#40;HttpServletResponse.SC_UNAUTHORIZED,
                                    authException.getMessage&#40;&#41;&#41;;
                        &#125;
                        Here's the stack trace:

                        Code:
                        java.lang.NullPointerException
                        	net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint.commence&#40;BasicProcessingFilterEntryPoint.java&#58;75&#41;
                        	com.perfretail.k2v1.webapp1.web.acegi.DelegatingAuthenticationEntryPoint.commence&#40;DelegatingAuthenticationEntryPoint.java&#58;84&#41;
                        	net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter.sendStartAuthentication&#40;SecurityEnforcementFilter.java&#58;253&#41;
                        	net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter.doFilter&#40;SecurityEnforcementFilter.java&#58;201&#41;
                        	net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter&#40;FilterChainProxy.java&#58;311&#41;
                        	net.sf.acegisecurity.providers.anonymous.AnonymousProcessingFilter.doFilter&#40;AnonymousProcessingFilter.java&#58;153&#41;
                        	net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter&#40;FilterChainProxy.java&#58;311&#41;
                        	net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter.doFilter&#40;BasicProcessingFilter.java&#58;212&#41;
                        	net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter&#40;FilterChainProxy.java&#58;311&#41;
                        	net.sf.acegisecurity.ui.AbstractProcessingFilter.doFilter&#40;AbstractProcessingFilter.java&#58;372&#41;
                        	net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter&#40;FilterChainProxy.java&#58;311&#41;
                        	net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter.doFilter&#40;HttpSessionContextIntegrationFilter.java&#58;217&#41;
                        	net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter&#40;FilterChainProxy.java&#58;311&#41;
                        	net.sf.acegisecurity.util.FilterChainProxy.doFilter&#40;FilterChainProxy.java&#58;179&#41;
                        	net.sf.acegisecurity.util.FilterToBeanProxy.doFilter&#40;FilterToBeanProxy.java&#58;125&#41;
                        And here's my delegating auth entry point (based on what your initial input):
                        Code:
                        import org.apache.log4j.Logger;
                        
                        import java.io.IOException;
                        import java.util.Map;
                        
                        import javax.servlet.ServletException;
                        import javax.servlet.ServletRequest;
                        import javax.servlet.ServletResponse;
                        import javax.servlet.http.HttpServletRequest;
                        
                        import net.sf.acegisecurity.AuthenticationException;
                        import net.sf.acegisecurity.intercept.web.AuthenticationEntryPoint;
                        
                        import org.springframework.util.Assert;
                        
                        public class DelegatingAuthenticationEntryPoint implements
                        		AuthenticationEntryPoint &#123;
                        	/**
                        	 * Logger for this class
                        	 */
                        	private static final Logger logger = Logger
                        			.getLogger&#40;DelegatingAuthenticationEntryPoint.class&#41;;
                        
                        	// ~ Instance fields
                        	// ========================================================
                        
                        	Map authenticationEntryPointMap;
                        
                        	// ~ Methods
                        	// ================================================================
                        
                        	public void commence&#40;ServletRequest request, ServletResponse response,
                        			AuthenticationException authException&#41; throws IOException,
                        			ServletException &#123;
                        		if &#40;logger.isDebugEnabled&#40;&#41;&#41; &#123;
                        			logger
                        					.debug&#40;"commence&#40;ServletRequest, ServletResponse, AuthenticationException&#41; - start"&#41;;
                        		&#125;
                        
                        		if &#40;!&#40;request instanceof HttpServletRequest&#41;&#41; &#123;
                        			throw new ServletException&#40;"only supports HttpServletRequest"&#41;;
                        		&#125;
                        		Assert.notNull&#40;authenticationEntryPointMap,
                        				"Required property authenticationEntryPointMap is null"&#41;;
                        
                        		HttpServletRequest httpRequest = &#40;HttpServletRequest&#41; request;
                        
                        		String servletPath = httpRequest.getServletPath&#40;&#41;;
                        		String extension = servletPath.substring&#40;servletPath.lastIndexOf&#40;'.'&#41; + 1&#41;;
                        
                        		AuthenticationEntryPoint entryPoint = &#40;AuthenticationEntryPoint&#41; authenticationEntryPointMap
                        				.get&#40;extension&#41;;
                        
                        		// attempt to get wildcard entry point
                        		if &#40;entryPoint == null&#41; &#123;
                        			entryPoint = &#40;AuthenticationEntryPoint&#41; authenticationEntryPointMap
                        					.get&#40;"*"&#41;;
                        		&#125;
                        
                        		if &#40;entryPoint != null&#41; &#123;
                        			entryPoint.commence&#40;request, response, authException&#41;;
                        		&#125;
                        
                        		if &#40;logger.isDebugEnabled&#40;&#41;&#41; &#123;
                        			logger
                        					.debug&#40;"commence&#40;ServletRequest, ServletResponse, AuthenticationException&#41; - end"&#41;;
                        		&#125;
                        	&#125;
                        
                        	public Map getAuthenticationEntryPointMap&#40;&#41; &#123;
                        		return authenticationEntryPointMap;
                        	&#125;
                        
                        	public void setAuthenticationEntryPointMap&#40;Map authenticationEntryPointMap&#41; &#123;
                        		this.authenticationEntryPointMap = authenticationEntryPointMap;
                        	&#125;
                        
                        &#125;

                        Comment


                        • #13
                          I was just looking at the CVS commit message for BasicProcessingFilterEntryPoint and noticed the following:

                          Added AuthenticationException to the commence method signature of the AutenticationEntryPoint. The best example of this
                          is the BasicProcessingFilterEntryPoint where the authException.getMessage() is used to send back an informative 401,
                          instead of just the error code.
                          In the event that credentials were provided but rejected, this makes sense and follows the spec (From http://www.w3.org/Protocols/rfc2616/...html#sec10.4.2):

                          If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information.
                          However, the problem seems to be that the case where the user has not provided credentials (ie, the 'Authorization' header has not been provided) is not taken into account. In that case, I believe no exception exists because the filter chain processor assumes that's a valid case and moves on to the next filter.

                          I'll admit this is a shot in the dark, but I see an AuthenticationCredentialsNotFoundException and a corresponding event, so perhaps I need to set up my delegating auth entry point to be a listener to this event somehow?

                          Comment


                          • #14
                            The problem was SecurityEnforcementFilter.java line 201.

                            I was sending null to the SecurityEnforcementFilter.sendStartAuthentication method, which in turn lead to null in the BasicProcessingFilterEntryPoint.

                            I've just checked a fix into CVS. The new code is:

                            Code:
                                    &#125; catch &#40;AccessDeniedException accessDenied&#41; &#123;
                                        if &#40;authenticationTrustResolver.isAnonymous&#40;
                                                SecureContextUtils.getSecureContext&#40;&#41;.getAuthentication&#40;&#41;&#41;&#41; &#123;
                                            if &#40;logger.isDebugEnabled&#40;&#41;&#41; &#123;
                                                logger.debug&#40;"Access is denied &#40;user is anonymous&#41;; redirecting to authentication entry point",
                                                    accessDenied&#41;;
                                            &#125;
                            
                                            sendStartAuthentication&#40;fi,
                                                new InsufficientAuthenticationException&#40;
                                                    "Full authentication is required to access this resource"&#41;&#41;;
                                    &#125;....

                            Comment


                            • #15
                              Thanks, Ben. I'll give it a go later today.

                              I appreciate your looking at this.

                              Rob

                              Comment

                              Working...
                              X