Christopher
Christopher

Reputation: 1645

Custom Spring filter causing next filter in chain not to fire?

I'm using a custom Spring Security filter which overrides AbstractAuthenticationProcessingFilter but I must have written it incorrectly as it seems to never call the rest of the filter chain. Specifically, I'm relying on the OpenEntityManagerInViewFilter filter to ensure Jackson+Hibernate can handle lazy-loaded objects.

If my web.xml has OpenEntityManagerInViewFilter first, everything works:

<filter>
    <filter-name>hibernateFilterChain</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>hibernateFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

If I place the springSecurityFilterChain at the top, however, my application behaves as though I didn't specify the OpenEntityManagerInViewFilter at all.

Here is my springSecurity.xml:

<?xml version="1.0"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-3.2.xsd">

<security:http entry-point-ref="restAuthenticationEntryPoint"
    use-expressions="true" create-session="stateless">

    <security:custom-filter ref="authenticationTokenProcessingFilter"
        position="FORM_LOGIN_FILTER" />
    <security:intercept-url pattern="/**"
        access="isAuthenticated()" />

    <security:logout />
</security:http>

<bean class="edu.ucdavis.dss.dw.security.CustomTokenAuthenticationFilter"
    id="authenticationTokenProcessingFilter">
    <constructor-arg type="java.lang.String">
        <value>/**</value>
    </constructor-arg>
</bean>

<security:authentication-manager>
    <security:authentication-provider
        user-service-ref="userService"></security:authentication-provider>
</security:authentication-manager>

<bean id="userService" class="edu.ucdavis.dss.dw.services.UserAuthenticationService"></bean>

</beans>

And finally, here is the CustomTokenAuthenticationFilter itself, which may be causing the issues:

public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    @Autowired @Qualifier("org.springframework.security.authenticationManager")
    private AuthenticationManager authenticationManager;

    public CustomTokenAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
        super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl));
        setAuthenticationManager(new NoOpAuthenticationManager());
        setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler());
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        String token = request.getParameter("token");

        if(token == null) {
            throw new AuthenticationServiceException("Token Missing");
        }

        Authentication authResponse;

        try {
            authResponse = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(token, "dssit"));
        } catch (AuthenticationException e) {
            throw new AuthenticationServiceException("Bad Token");
        }

        return authResponse;
    }
}

In summary: I made a custom security filter and it appears not to call any filters which are listed after it. If I remove my custom filter and use something built-in like security:http-basic, it works fine.

Thanks in advance for any help you can offer.

Upvotes: 2

Views: 6618

Answers (4)

David Riccitelli
David Riccitelli

Reputation: 7812

The AbstractAuthenticationProcessingFilter class has the following method which can be overridden:

protected boolean requiresAuthentication(HttpServletRequest request,
        HttpServletResponse response)

This method is called before initiating the authentication with the filter (in doFilter):

if (!requiresAuthentication(request, response)) {
    chain.doFilter(request, response);

    return;
}

This method should be overridden by the subclass to check whether it can authenticate the request: it should return false if the token is not present or invalid.

For example:

@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {

    if (!super.requiresAuthentication(request, response)) {
       // We're not required to authenticate this request (ant matchers).
       return false;
    }

    if (null == request.getParameter("token")) {
       // We can't authenticate this request, because the token is missing.
       return false;
    }

    try {
      authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(token, "dssit"));
    } catch (AuthenticationException e) {
      // We can't authenticate this token because an exception occurred.
      return false;
    }

    // We can authenticate this request.
    return true;
}

Upvotes: 1

mengjiann
mengjiann

Reputation: 285

What Christopher mentioned in his reply is correct, but I thought it would be better to show some code. For my case, I have implemented custom authentication method by extending UsernamePasswordAuthenticationFilter and also custom AuthenticationSuccessHandler implementation.

In the beginning, I have the implementation as below for the authenticationSuccessHandler to return the response

httpServletResponse.setStatus(HttpServletResponse.
httpServletResponse.setContentType("application/json");
httpServletResponse.getWriter().write(loginSuccessResponse);
httpServletResponse.getWriter().flush();

But, when I turned on debugging using the following:

@EnableWebSecurity(debug = true)

and enable debug logging for org.springframework.security.web.FilterChainProxy.

I realized that the security filter chain stopped at the custom implementation UsernamePasswordAuthenticationFilter. Then, I found Christopher reply and did as what he shared, which is to:

Redirect the response in AuthenticationSuccessHandler to another endpoint:

public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler
    implements AuthenticationSuccessHandler  {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

        httpServletResponse.sendRedirect(ApiResourceConstant.POST_LOGIN);
    }
}

You can explore on SimpleUrlAuthenticationSuccessHandler too. Then, you should be able to see all the filter chain is fired from the log.

Hope it helps :)

Upvotes: 1

Christopher
Christopher

Reputation: 1645

From what I can tell, a Spring authentication filter is meant to authenticate a request and then redirect somewhere, starting a whole new request. This means that the filter chain is purposefully halted. The idea is that, once authenticated and in a new request, an authentication session will already exist so the authentication filter will not have to redirect again. At least, this is my understanding from a little debugging.

In order to get around this behavior, you have to implement your own AbstractAuthenticationProcessingFilter, AuthenticationEntryPoint, AbstractAuthenticationToken, AuthenticationProvider, and SimpleUrlAuthenticationSuccessHandler.

Upvotes: 2

Dino Tw
Dino Tw

Reputation: 3321

Do you want to try to add

public void doFilter(javax.servlet.ServletRequest req,
        javax.servlet.ServletResponse res,
        javax.servlet.FilterChain chain)
          throws IOException,
                 javax.servlet.ServletException {

    chain.doFilter(req, res);
}

to the CustomTokenAuthenticationFilter class?

Upvotes: 2

Related Questions