Question

I have configured Spring Security to show custom error page during authorisation process. Everything works fine in Firefox, Opera and Chrome but not in IE8. This browser shows its default page for 403 http code instead of defined one in spring config file. I make use of preauthenticated scenario so by the time I check user privileges, he or she is already authenitcated and I only make decision whether to permit him/her access to requested url.

Here is my configuration of web.xml

<filter>
    <filter-name>filterChainProxy</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>filterChainProxy</filter-name>
     <url-pattern>/*</url-pattern>
</filter-mapping>

<login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>Testing realm</realm-name>
</login-config>

<security-role>
    <role-name>*</role-name>
</security-role>

<security-constraint>
    <display-name>free access</display-name>
    <web-resource-collection>
        <web-resource-name>free access pages</web-resource-name>
        <url-pattern>/denied.html</url-pattern>
        <http-method>GET</http-method>
    </web-resource-collection>
</security-constraint>



<!--  only enforce authentication, authorisation performed by application  -->
<security-constraint>
<display-name>restricted access</display-name>
    <web-resource-collection>
        <web-resource-name>All areas</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>*</role-name>
    </auth-constraint>
</security-constraint>

And this is my security related configuration:

<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
    <sec:filter-chain-map path-type="ant">
        <sec:filter-chain pattern="/**"
            filters="sif,j2eePreAuthFilter,logoutFilter,etf,fsi" />
    </sec:filter-chain-map>
</bean>

<bean id="sif" class="org.springframework.security.web.context.SecurityContextPersistenceFilter" />

<sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider ref='preAuthenticatedAuthenticationProvider' />
</sec:authentication-manager>

<bean id="preAuthenticatedAuthenticationProvider"
    class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
    <property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService" />
</bean>

<bean id="preAuthenticatedUserDetailsService"
    class="org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService" />

<bean id="j2eePreAuthFilter"
    class="org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="authenticationDetailsSource">
        <bean class="pl.company.spring.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSourceFromDatabase">
            <property name="mappableRolesRetriever">
                <bean class="pl.company.spring.DatabaseMappableAttributesRetriever" />
            </property>
            <property name="userRoles2GrantedAuthoritiesMapper">
                <bean class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
                    <property name="convertAttributeToUpperCase" value="true" />
                </bean>
            </property>
        </bean>
    </property>
</bean>

<!-- <bean id="preAuthenticatedProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />  -->

<bean id="preAuthenticatedProcessingFilterEntryPoint" class="pl.ivmx.cbp.spring.HTTP403ForbiddenEntryPoint">
    <property name="errorPage" value="/denied.html"/>
</bean>

<bean id="logoutFilter"
    class="org.springframework.security.web.authentication.logout.LogoutFilter">
    <constructor-arg value="/" />
    <constructor-arg>
        <list>
 <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
    </list>
    </constructor-arg>
</bean>

<bean id="servletContext"
    class="org.springframework.web.context.support.ServletContextFactoryBean" />

<bean id="etf" class="org.springframework.security.web.access.ExceptionTranslationFilter">
    <property name="authenticationEntryPoint" ref="preAuthenticatedProcessingFilterEntryPoint" />
 <property name="accessDeniedHandler" ref="accessDeniedHandler"/>
</bean>

<bean id="accessDeniedHandler" class="pl.company.spring.MyAccessDeniedHandlerImpl">
    <property name="errorPage" value="/denied.html"/>
</bean>

<bean id="httpRequestAccessDecisionManager"
    class="org.springframework.security.access.vote.UnanimousBased">
    <property name="allowIfAllAbstainDecisions" value="false" />
<property name="decisionVoters">
    <list>
        <ref bean="roleVoter" />
        <ref bean="permissionVoter"/>
        <ref bean="authenticationVoter"/>
    </list>
</property>
</bean>

<bean id="fsi" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
    <property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager" />
<property name="securityMetadataSource">
<sec:filter-invocation-definition-source>
        <! handled by my own voter -->
   <sec:intercept-url pattern="/cbp/rpc/ppecounter*" access="permission_ppecounter"/> 
   <!-- handled by role voter -->
       <sec:intercept-url pattern="/**" access="ROLE_USER" />
   </sec:filter-invocation-definition-source>
</property>
</bean>

<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter" />


<bean id="permissionVoter" class="pl.company.spring.PermissionVoter"/>

<bean id="authenticationVoter" class="org.springframework.security.access.vote.AuthenticatedVoter"/>

<bean id="securityContextHolderAwareRequestFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter" />

What i tried to do was adding following snippet to web.xml:

<error-page>
    <error-code>403</error-code>
    <location>/denied.html</location>
</error-page>

But it didn't helped. I also inspected code of AccessDeniedHandlerImpl from org.springframework.security.web.access package. As its handle method performs forward to custom error page I checked if response isn't already commited:

   public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
        throws IOException, ServletException {
    if (!response.isCommitted()) {
        if (errorPage != null) {
            // Put exception into request scope (perhaps of use to a view)
            request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);

            // Set the 403 status code.
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);

            // forward to error page.
            RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
            dispatcher.forward(request, response);
        } else {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
        }
    }
}

I had overriden this class and added my custom logging which showed that response wasn't already commited and forward of request to custom error page was made.

The last point was Http403ForbiddenEntryPoint class used by ExceptionTranslationFilter. Its commence method looks like this:

public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) throws IOException,
        ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Pre-authenticated entry point called. Rejecting access");
    }
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
}

I have also overriden this method to not send default error page but to do nothing. In second try i replicated code from AccessDeniedHandlerImpl to perform forward. Againt it didn't help for IE8. The debug log from spring packets looks as follows:

11:22:37,288 DEBUG [ExceptionTranslationFilter] Access is denied (user is not anonymous); delegating to AccessDeniedHandler
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.UnanimousBased.decide(UnanimousBased.java:90)
at 
11:22:37,289 DEBUG [FilterChainProxy] /denied.html at position 1 of 5 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
11:22:37,289 DEBUG [FilterChainProxy] /denied.html at position 1 of 5 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
11:22:37,290 DEBUG [FilterChainProxy] /denied.html at position 2 of 5 in additional filter chain; firing Filter: 'J2eePreAuthenticatedProcessingFilter'
11:22:37,290 DEBUG [FilterChainProxy] /denied.html at position 2 of 5 in additional filter chain; firing Filter: 'J2eePreAuthenticatedProcessingFilter'
11:22:37,290 DEBUG [J2eePreAuthenticatedProcessingFilter] Checking secure context token: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@f9fdea61: Principal: org.springframework.security.core.userdetails.User@6038c01: Username: jarek; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Not granted any authorities; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; []; Not granted any authorities
11:22:37,290 DEBUG [J2eePreAuthenticatedProcessingFilter] Checking secure context token: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@f9fdea61: Principal: org.springframework.security.core.userdetails.User@6038c01: Username: jarek; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Not granted any authorities; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; []; Not granted any authorities
11:22:37,290 DEBUG [FilterChainProxy] /denied.html at position 3 of 5 in additional filter chain; firing Filter: 'LogoutFilter'
11:22:37,290 DEBUG [FilterChainProxy] /denied.html at position 3 of 5 in additional filter chain; firing Filter: 'LogoutFilter'
11:22:37,290 DEBUG [FilterChainProxy] /denied.html at position 4 of 5 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
 11:22:37,290 DEBUG [FilterChainProxy] /denied.html at position 4 of 5 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
 11:22:37,290 DEBUG [FilterChainProxy] /denied.html at position 5 of 5 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
11:22:37,290 DEBUG [FilterChainProxy] /denied.html at position 5 of 5 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
11:22:37,290 DEBUG [FilterChainProxy] /denied.html reached end of additional filter chain; proceeding with original chain
11:22:37,290 DEBUG [FilterChainProxy] /denied.html reached end of additional filter chain; proceeding with original chain
11:22:37,290 DEBUG [ExceptionTranslationFilter] Chain processed normally
11:22:37,290 DEBUG [ExceptionTranslationFilter] Chain processed normally
11:22:37,291 DEBUG [HttpSessionSecurityContextRepository] HttpSession being created as SecurityContext is non-default
11:22:37,291 DEBUG [HttpSessionSecurityContextRepository] HttpSession being created as SecurityContext is non-default
11:22:37,291 WARN  [HttpSessionSecurityContextRepository] Failed to create a session, as response has been committed. Unable to store SecurityContext.
11:22:37,291 WARN  [HttpSessionSecurityContextRepository] Failed to create a session, as response has been committed. Unable to store SecurityContext.
11:22:37,291 DEBUG [SecurityContextPersistenceFilter] SecurityContextHolder now cleared, as request processing completed
11:22:37,291 DEBUG [SecurityContextPersistenceFilter] SecurityContextHolder now cleared, as request  processing completed

I also tried adding dispatching types to Spring filter:

<filter-mapping>
    <filter-name>filterChainProxy</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>ERROR</dispatcher>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

But again IE8 was reluctant to cooperate. Any hints how to "force" IE8 to show /denied.html would be welcomed.

Was it helpful?

Solution

Make the error page (denied.html) bigger than 512 bytes. Pad the HTML with spaces or whatever.

On the client side, you can disable "Friendly error messages" in Internet Settings and IE will show your page even if it is smaller than 512 bytes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top