Custom filter in Spring Security

Until now we've working with filters belonging to Spring Security project. However we can also add our own custom filters which must only follow some basic rules.

In this article we'll discover how to implement custom security filter in project using Spring Security. At the begin we'll present the main implementation rules. After that we'll write our own filter and see it in action.

Custom Spring Security filter rules

Custom filter can be implemented as normal Java's filter and as specific Spring bean. But in both cases it must follow some rules. One of the most important rules is not stopping of filter chain execution. It means that our filter must call doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) method of javax.servlet.FilterChain.

Suppose that we want to implement a filter based on Spring framework. This kind of filter must extend GenericFilterBean abstract class. This class implements Java's Filter interface and provide an implementation for filter initialization method (init()). Also a default implementation for destroying method is defined (destroy()). However, the body of the method is empty and it should be readapted by all filter beans extending GenericFilterBean. These beans must also define doFilter method to make some security actions on current request.

A special kind of GenericFilterBean was introduced to live in Servlet 3.0 environment. This version added a possibility to treat the requests in separate threads. To avoid multiple filters execution for this case, Spring Web project defines a special kind of filter, OncePerRequestFilter. It extends directly GenericFilterBean and, as this class, is located in org.springframework.web.filter package. OncePerRequestFilter defines doFilter method. Inside it checks if given filter was already applied by looking for "${className}.FILTER" attribute corresponding to true in request's parameters. In additionally, it defines an abstract doFilterInternal((HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) method. Its implementations will contain the code to execute by given filter if the filter hasn't been applied:

// Do invoke this filter...
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
  doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
  // Remove the "already filtered" request attribute for this request.
  request.removeAttribute(alreadyFilteredAttributeName);
}

Note also that if you try to override Spring Security's default filter, as UsernamePasswordAuthenticationFilter, you should use abstract class extended directly by overriden filter. In the case of authentication filters, this class will be AbstractAuthenticationProcessingFilter.

Filter can be set up to Spring context as a <custom-filter /> element of Spring Security namespace. It can also be defines a bean and defined in manually constructed filter chain list. In the first case, custom-filter element takes some supplementary elements which are used to determine its position in security filter chain. The position can be specified in 1 of 3 ways:
- by setting directly the position with position attribute.
- by specifying before which filter customized filter must be executed with before attribute.
- by specifying after which filter customized filter must be execute with after attribute.

Write custom Spring Security filter

After learning some basic rules, we can start to write our custom Spring Security filter. This filter will help us to authenticate user for one-shot action. To understand this problem better, take a look on below example:
- user receives a newsletter mail where he's invited to vote for more beautiful holidays picture. The URL contains a hash parameter used to authenticate user in the page.
- when user clicks on given URL, it will arrive to voting page as already authenticated user

To implement this feature we'll use a simple in-memory storage based. It should be based on some more dynamic solution. But we're coding that only for learning purposes so clearest solution is prefered. Thanks to that, we can pass immediately to code our custom Spring Security filter:

public class OneShotActionFilter extends GenericFilterBean {

  private static final Logger LOGGER = LoggerFactory.getLogger(OneShotActionFilter.class);
  private static Map<String, String> users = new HashMap<String, String>();
  static {
    users.put("0000000000001", "bartosz");
    users.put("0000000000002", "admin");
    users.put("0000000000003", "mod");
  }
  private static final String PARAM_NAME = "uio";
  private AuthenticationManager authenticationManager;
  private UserDetailsService userDetailsService;
  private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

  private enum AuthenticationStates {
    REDIRECT, CONTINUE;
  }

  public void setAuthenticationManager(AuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;
  }

  public void setUserDetailsService(UserDetailsService userDetailsService) {
    this.userDetailsService = userDetailsService;
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,
    ServletException {
    LOGGER.debug("One shot filter invoked");
    if (attemptAuthentication(request) == AuthenticationStates.REDIRECT) {
      // Note that we should handle that dynamically but for learning purposes we'll consider that one-shot
      // authentication works only for this URL
      this.redirectStrategy.sendRedirect((HttpServletRequest) request, (HttpServletResponse) response,
        "/secret/one-shot-action");
    } else {
      LOGGER.debug("User was not correctly authenticated, continue filter chain");
      // continue execution of all other filters
      // You can test the code without this fragment in the pages without ?uio parameter. You should see blank page because of
      // security filter chain interruption.
      filterChain.doFilter(request, response);
    }
  }

  private AuthenticationStates attemptAuthentication(ServletRequest request) {
    AuthenticationStates state = AuthenticationStates.CONTINUE;
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String code = request.getParameter(PARAM_NAME);
    if ((authentication == null || !authentication.isAuthenticated()) && code != null &&
        users.containsKey(code)) {
        LOGGER.debug("Checking user for code "+code);
        UserDetails user = userDetailsService.loadUserByUsername(users.get(code));
        LOGGER.debug("Found user from code ("+users.get(code)+"). User found is "+user);
        if (user != null) {
          users.remove(code);
          UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user.getUsername(),
            user.getPassword());
        authentication = this.authenticationManager.authenticate(authRequest);
        if (authentication != null && authentication.isAuthenticated()) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
            state = AuthenticationStates.REDIRECT;
        }
      }
    }
    return state;
  }
}

As you can see, authentication code is pretty simple. Firstly, we try to authenticate current user only when he's not already authenticated and if the request contains one-shot action parameter. If they're corresponding user, we retrieve Authentication object directly from UserDetails instance and put it into security context. In this way, user created with that will be able to persist through different requests thanks to the actions made in another filters (and more precisely in SecurityContextPersistenceFilter). Note that we don't continue execution of filter chain because Authentication object stored in security context could be modified. And we don't want it. Chain is continued only in the situation when user couldn't be authenticated from current request.

To correctly handle persistence of generated Authentication object in security context we must invoke our OneShotActionFilter after SecurityContextPersistenceFilter. To do that, we can use following configuration inside <security:http /> element:

<security:http authentication-manager-ref="frontend" auto-config="true" use-expressions="true" access-denied-page="/access-denied">
  <-- other filters are defined here -->
  <security:custom-filter ref="oneShootAuthFilter" after="CONCURRENT_SESSION_FILTER"/>
</security:http>

<bean id="oneShootAuthFilter" class="com.waitingforcode.security.filter.OneShotActionFilter">
  <property name="authenticationManager" ref="frontend" />
  <property name="userDetailsService" ref="inMemoryUserService" />
</bean>
show full security configuration

To test the filter, we can try to access to http://localhost:8080/?uio=0000000000001. You should be redirected (but only once) to http://localhost:8080/secret/one-shot-action page.

This article covered the implementation of custom Spring Security filter. At the begin we discovered some main rules to respected before put it in place. After that we put these points in practice. Our constructed filter was responsible to manage one-shot user authentication through passed parameter. It was based on Spring's GenericFilterBean class, one of standards used in Spring Security project. We could also use OncePerRequestFilter class but our application runs in Servlet 2, request treatment in separate threads doesn't exist.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!