Core Java

How to exclude a URL from a Filter

By default, filters doesn’t support excluding a specific URL pattern, whenever you define a URL pattern for a filter then any request matching this pattern is handled by the filter without exceptions.

The simplest way for excluding URLs from a filter is to map your filter to a very specific pattern. This is feasible when done in early development stages, however it could be a cumbersome process if you modify the URL pattern of an existing filter in a production environment as you have to remap all the existing servlet URLs to achieve your purpose.

In this tutorial, we show how to programatically add an exclude functionality to an existing filter.

1- Custom Filter

A custom filter is a filter which you can control. i.e. you have all the rights to modify its source code.

Suppose we have an existing web application which authenticates user requests through LDAP. All the servlet requests pass through LDAPAuthenticationFilter which is mapped to /* as the following:

<filter>
    <filter-name>LDAPAuthenticationFilter</filter-name>
    <filter-class>com.programmer.gate.filters.LDAPAuthenticationFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>LDAPAuthenticationFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Our filter simply authenticates the request and calls chain.doFilter() afterwards:

LDAPAuthenticationFilter.java

package com.programmer.gate.filters;
 
import java.io.IOException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
 
public class LDAPAuthenticationFilter implements Filter{
 
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
 
        // Authenticate the request through LDAP
        System.out.println("Authenticating the request through LDAP");
        
        // Forward the request to the next filter or servlet in the chain.
        chain.doFilter(req, resp);
    }
    
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    
    public void destroy() {
        // TODO Auto-generated method stub
    }
}

Now, suppose we want to create a servlet which requires a simple database authentication and needs not to pass through LDAP. The first thing we think of it to create a new filter and map it to the specific URL pattern of the new servlet.

So we create a new filter named as DatabaseAuthenticationFilter which simply authenticates the request through database and calls chain.doFilter() afterwards:

package com.programmer.gate.filters;
 
import java.io.IOException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
public class DatabaseAuthenticationFilter implements Filter{
 
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
 
        // Authenticate the request through database then forward the request to the next filter or servlet in the chain
        System.out.println("Authenticating the request through database");
        
        chain.doFilter(req, resp);
    }
    
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub
        
    }
    
    public void destroy() {
        // TODO Auto-generated method stub
        
    }
}

We define our filter under web.xml to handle only specific URLs starting with /DatabaseAuthenticatedServlet:

<filter>
    <filter-name>DatabaseAuthenticationFilter</filter-name>
    <filter-class>com.programmer.gate.filters.DatabaseAuthenticationFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>DatabaseAuthenticationFilter</filter-name>
    <url-pattern>/DatabaseAuthenticatedServlet/*</url-pattern>
</filter-mapping>

The problem here is that requests like /DatabaseAuthenticatedServlet would also match the root URL pattern “/*”, i.e. our request would pass through 2 authentication processes: LDAP and Database, the ordering depends on which filter is defined first under web.xml.

In order to solve this, we need to modify LDAPAuthenticationFilter so that it excludes URLs starting with /DatabaseAuthenticatedServlet. What people normally do is to statically check over the servlet URL of the request inside doFilter() method and simply bypass the authentication process when found.

Here we go a step further and implement a more dynamic solution which allows us to manage the excluded URLs through web.xml.

Following are the steps for adding the exclude feature to LDAPAuthenticationFilter:

  • Add a new field called excludedUrls of type List<String>:
    private List excludedUrls;
    
  • Inside init() method, read a configuration attribute called excludedUrls using FilterConfig, the attribute is supposed to be comma-separated so that we exclude as much URLs as we need.
    public void init(FilterConfig filterConfig) throws ServletException {
        String excludePattern = filterConfig.getInitParameter("excludedUrls");
        excludedUrls = Arrays.asList(excludePattern.split(","));
    }
    
  • Modify doFilter() in order to checks if the requested URL belongs to the list of predefined excluded URLs, if so then just forward the request to the next filter or servlet in the chain, otherwise do your authentication logic.
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
                throws IOException, ServletException {
     
        String path = ((HttpServletRequest) req).getServletPath();
            
        if(!excludedUrls.contains(path))
        {
            // Authenticate the request through LDAP
            System.out.println("Authenticating the request through LDAP");
        }
            
        // Forward the request to the next filter or servlet in the chain.
        chain.doFilter(req, resp);
    }
    
  • Now inside web.xml, you can control which URL to exclude from LDAP authentication without any single code change:
    <filter>
        <filter-name>LDAPAuthenticationFilter</filter-name>
        <filter-class>com.programmer.gate.filters.LDAPAuthenticationFilter</filter-class>
        <init-param>
            <param-name>excludedUrls</param-name>
            <!-- Comma separated list of excluded servlets  -->
            <param-value>/DatabaseAuthenticatedServlet,/UnAuthenticatedServlet</param-value>
        </init-param>
    </filter>
    

This is how LDAPAuthenticationFilter looks like after adding the exclude functionality:

package com.programmer.gate.filters;
 
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
 
public class LDAPAuthenticationFilter implements Filter{
    
    private List excludedUrls;
 
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
 
        String path = ((HttpServletRequest) req).getServletPath();
        
        if(!excludedUrls.contains(path))
        {
            // Authenticate the request through LDAP
            System.out.println("Authenticating the request through LDAP");
        }
        
        // Forward the request to the next filter or servlet in the chain.
        chain.doFilter(req, resp);
    }
    
    public void init(FilterConfig filterConfig) throws ServletException {
        String excludePattern = filterConfig.getInitParameter("excludedUrls");
        excludedUrls = Arrays.asList(excludePattern.split(","));
    }
    
    public void destroy() {
        // TODO Auto-generated method stub
    }
}

2- Third-party Filter

The third-party filters are the filters which you can’t control. i.e. you can’t modify their source code.

In this section, we alter our example a bit and use CAS authentication instead of LDAP. This is how we define our CAS authentication filter in web.xml:

<filter>
  <filter-name>CAS Authentication Filter</filter-name>
  <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
  <init-param>
    <param-name>casServerLoginUrl</param-name>
    <param-value>https://localhost:8443/cas/login</param-value>
  </init-param>
  <init-param>
    <param-name>serverName</param-name>
    <param-value>localhost</param-value>
  </init-param>
</filter>
<filter-mapping>
    <filter-name>CAS Authentication Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

CAS authentication is done through a third-party library, now in order to support database authentication we can’t modify the source code of CAS as we did in the previous example with LDAP.

The solution for excluding URLs from a third-party filter is to wrap it with a new custom filter which just adds the exclude functionality and delegates the filter logic to the wrapped class.

Following are the steps for adding exclude functionality to CAS authentication:

  • Create a new filter called CASCustomAuthenticationFilter as the following:
    public class CASCustomAuthenticationFilter implements Filter{
        
        private AuthenticationFilter casAuthenticationFilter = new AuthenticationFilter();
        private List excludedUrls;
     
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
                throws IOException, ServletException {
     
            String path = ((HttpServletRequest) req).getServletPath();
            
            if(!excludedUrls.contains(path))
            {
                // Authenticate the request through CAS
                casAuthenticationFilter.doFilter(req,resp,chain);
            }
            
            // Forward the request to the next filter or servlet in the chain.
            chain.doFilter(req, resp);
        }
        
        public void init(FilterConfig arg0) throws ServletException {
     
            String excludePattern = filterConfig.getInitParameter("excludedUrls");
            excludedUrls = Arrays.asList(excludePattern.split(","));
            
            casAuthenticationFilter.init();
        }
        
        public void destroy() {
            casAuthenticationFilter.destroy();
        }
    }
    

    Our custom filter wraps the CAS authentication filter through composition, its main purpose is to just manage which URLs to be authenticated through CAS , while we didn’t touch the CAS authentication procedure.

  • In web.xml, we change the filter definition to use CASCustomAuthenticationFilter instead of the default CAS implementation:
    <filter>
      <filter-name>CAS Authentication Filter</filter-name>
      <filter-class>com.programmer.gate.filters.CASCustomAuthenticationFilter</filter-class>
      <init-param>
        <param-name>casServerLoginUrl</param-name>
        <param-value>https:localhost:8443/cas/login</param-value>
      </init-param>
      <init-param>
        <param-name>serverName</param-name>
        <param-value>localhost</param-value>
      </init-param>
      <init-param>
        <param-name>excludeUrls</param-name>
        <param-value>/DatabaseAuthenticatedServlet</param-value>
      </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Authentication Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

That’s it, please leave your thoughts in the comments section below.

Published on Java Code Geeks with permission by Hussein Terek, partner at our JCG program. See the original article here: How to exclude a URL from a Filter

Opinions expressed by Java Code Geeks contributors are their own.

Hussein Terek

Hussein is a senior software engineer with 5 years of experience in software design, development and integration. He is the author and founder of Programmer Gate (www.programmergate.com) blog.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button