Implementación de Servlet Filters Personalizados en Spring Boot

En aplicaciones web de Spring Boot basadas en Servlets, a menudo es necesario declarar Servlet Filters personalizados para ejecutar lógica específica. Esto incluye tareas como la implementación de sistemas de autorización sencillos, el filtrado de cabeceras de solicitud o la protección contra ataques XSS. Este artículo explora dos métodos para declarar Servlet Filters personalizados en Spring Boot, definiendo su alcance y el orden de ejecución.

Declaración de Filters Personalizados

La forma más básica de declarar un Servlet Filter en Spring Boot es registrar una implementación de la interfaz javax.servlet.Filter como un bean de Spring.

@Configuration
public class FilterConfiguration {

    @Bean
    public Filter processRequestFilter() {
        return (request, response, chain) -> {
            // Lógica de negocio aquí
            System.out.println("Procesando solicitud...");
            chain.doFilter(request, response);
        };
    }

    @Bean
    public Filter processResponseFilter() {
        return (request, response, chain) -> {
            // Lógica de negocio aquí
            chain.doFilter(request, response);
            System.out.println("Procesando respuesta...");
        };
    }
}

Este enfoque es sencillo pero tiene limitaciones: no garantiza un orden de ejecución específico y, por defecto, se aplica a todas las solicitudes (equivalente a /* en la sintaxis de Ant). Para abordar estas limitaciones, se requieren métodos más avanzados.

Ordenación de Filters

Para controlar el orden de ejecución de los filters, se puede utilizar la anotación @Order de Spring o implementar la interfaz Ordered. Dado que la anotación @Order debe aplicarse a una clase concreta, y para facilitar la configuración mediante Java, es conveniente implementar la interfaz OrderedFilter (que combina Filter y Ordered). La configuración anterior se puede refinar de la sigueinte manera:

@Configuration
public class FilterConfiguration {

    @Bean
    public OrderedFilter responseFilter() {
        // Asumiendo que ResponseFilter implementa OrderedFilter y toma patrones de URL
        return new ResponseFilter("/api/*");
    }

    @Bean
    public OrderedFilter requestFilter() {
        // Asumiendo que RequestFilter implementa OrderedFilter y toma patrones de URL
        return new RequestFilter("/api/*");
    }
}

Los filters se ejecutan en orden ascendente según su valor de order; un valor menor indica una ejecución más temprana, similar a la prioridad de inicialización de beans en Spring.

Ámbito de los Filters Personalizados

Para definir el ámbito de aplicación de un filter, se puede interceptar la URI de la solicitud y compararla con patrones de tipo Ant. Un patrón de Ant es una cadena que representa una ruta de archivo o URL con comodines. Para encapsular esta lógica, se puede crear una clase base abstracta que maneje la coincidencia de patrones y proporcione un método de gancho para la lógica específica del filter.

A continuación, se presenta una clase base abstracta que implementa OrderedFilter y maneja la lógica de coincidencia de patrones Ant:

package com.example.filters;

import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;

/**
 * Clase base abstracta para Filters con coincidencia de patrones Ant y ordenación.
 */
public abstract class AbstractScopedFilter implements OrderedFilter {

    private final Set<string> urlPatterns;

    public AbstractScopedFilter(String... patterns) {
        Set<string> patternSet = new LinkedHashSet<>();
        Collections.addAll(patternSet, patterns);
        this.urlPatterns = Collections.unmodifiableSet(patternSet);
    }

    /**
     * Método abstracto para la lógica de negocio específica del Filter.
     *
     * @param request  la solicitud
     * @param response la respuesta
     * @throws IOException      si ocurre un error de E/S
     * @throws ServletException si ocurre un error de Servlet
     */
    public abstract void handleRequest(ServletRequest request, ServletResponse response) throws IOException, ServletException;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (matches(request)) {
            handleRequest(request, response);
        }
        chain.doFilter(request, response);
    }

    /**
     * Verifica si la solicitud actual coincide con alguno de los patrones definidos.
     *
     * @param request la solicitud
     * @return true si coincide, false en caso contrario
     */
    private boolean matches(ServletRequest request) {
        if (CollectionUtils.isEmpty(urlPatterns)) {
            return true; // Si no hay patrones, coincide con todo
        }

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestUri = httpRequest.getRequestURI();
        AntPathMatcher matcher = new AntPathMatcher();

        return urlPatterns.stream()
                .anyMatch(pattern -> matcher.match(pattern, requestUri));
    }

    // Método para obtener los patrones de URL (opcional, para introspección)
    public Set<string> getUrlPatterns() {
        return urlPatterns;
    }
}
</string></string></string>

Ahora, se puede crear una implementación concreta de este filtro, por ejemplo, para registrar la URI de la solicitud:

import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Slf4j
public class LoggingRequestFilter extends AbstractScopedFilter {

    public LoggingRequestFilter(String... urlPatterns) {
        super(urlPatterns);
    }

    @Override
    public void handleRequest(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        log.info("Solicitud recibida en: {}", httpRequest.getRequestURI());
    }

    @Override
    public int getOrder() {
        // Define la prioridad de este filtro
        return 1;
    }
}

Finalmente, estos filtros personalizados se registran como beans de Spring, y si se necesitan múltiples filtros con un orden específico, se aplica el método descrito en la sección anterior sobre ordenación.

Mecanismo de Registro de Spring Boot

Spring Boot ofrece una forma más integrada de gestionar la configuración de filtros, incluyendo su orden y ámbito, mediante la clase FilterRegistrationBean.

package com.example.configuration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Configuration
@Slf4j
public class SpringBootFilterConfig {

    @Bean
    public FilterRegistrationBean<filter> registrationFilterOne() {
        FilterRegistrationBean<filter> bean = new FilterRegistrationBean<>();
        bean.setName("firstFilter");
        bean.setOrder(1); // Menor número = mayor prioridad
        bean.setFilter((request, response, chain) -> {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            log.info("Filtro 1 - Solicitud: {}", httpRequest.getRequestURI());
            chain.doFilter(request, response);
        });
        bean.addUrlPatterns("/secure/*"); // Aplica solo a rutas bajo /secure
        return bean;
    }

    @Bean
    public FilterRegistrationBean<filter> registrationFilterTwo() {
        FilterRegistrationBean<filter> bean = new FilterRegistrationBean<>();
        bean.setName("secondFilter");
        bean.setOrder(2);
        bean.setFilter((request, response, chain) -> {
            // Lógica del segundo filtro, que se ejecuta después del primero si la ruta coincide
            chain.doFilter(request, response);
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            log.info("Filtro 2 - Respuesta: Código {}", httpServletResponse.getStatus());
        });
        bean.addUrlPatterns("/secure/*");
        return bean;
    }
}
</filter></filter></filter></filter>

Puntos Clave

  • Cada instancia de FilterRegistrationBean se asocia con un único Filter.
  • Si se registran múltiples FilterRegistrationBean con el mismo nombre, solo el primero será efectivo. Es crucial asignar nombres únicos mediante setName(String name).
  • El orden de ejecución se controla con el método setOrder(int order).

En resumen, tanto la creación manual de filtros con patrones personalizados como el uso del mecanismo de registro proporcionado por Spring Boot permiten implementar filtros de manera efectiva. Mientras que el enfoque personalizado fomenta una mejor comprensión de la programación orientada a objetos y la abstracción, el método de Spring Boot ofrece una solución más directa y simplificada para la gestión de filtros.

Etiquetas: Spring Boot Servlet Filter java configuration filter order ant path matching

Publicado el 9-2 19:44