Principios de Auto-registro de CorsFilter para Manejo de CORS en Spring Boot

Introducción al Manejo de CORS

El mecanismo de Compartición de Recursos de Origen Cruzado (CORS) en aplicaciones web modernas permite solicitudes seguras entre dominios distintos. Spring Boot integra este soporte mediante configuración automática, simplificando la implementación de políticas CORS.

Enterfaces y Claves Fundamentales

Dos interfaces centrales guían el procesamiento de CORS:


public interface EstrategiaCors {
    boolean gestionarSolicitud(@Nullable ConfiguracionCors configuracion,
                              HttpServletRequest peticion,
                              HttpServletResponse respuesta) throws IOException;
}

public interface FuenteConfiguracionCors {
    ConfiguracionCors obtenerConfiguracion(HttpServletRequest peticion);
}

Las implementaciones principales incluyen un filtro basado en patrones de URL:


public class FiltroCORS implements Filter {
    private final FuenteConfiguracionCors fuenteConfig;
    private EstrategiaCors estrategia = new EstrategiaCorsDefault();
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
                        FilterChain cadena) throws IOException, ServletException {
        // Lógica central de procesamiento CORS
    }
}

public class FuenteConfigURIBaseada implements FuenteConfiguracionCors {
    private final Map<string configuracioncors=""> configuraciones = new LinkedHashMap<>();
    
    @Override
    public ConfiguracionCors obtenerConfiguracion(HttpServletRequest peticion) {
        // Búsqueda basada en patrones de ruta
    }
}
</string>

Configuración Automática en Spring Boot

La auto-configuración se activa bajo condiciones específicas mediante una clase de configuración:


@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Filter.class)
@ConditionalOnProperty(prefix = "spring.mvc.cors", name = "enabled",
                      havingValue = "true", matchIfMissing = false)
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
public class ConfiguracionCorsAuto {
    
    @Bean
    @ConditionalOnMissingBean
    public FiltroCORS filtroCors(FuenteConfiguracionCors fuente) {
        return new FiltroCORS(fuente);
    }
    
    @Bean
    @ConditionalOnMissingBean
    public FuenteConfiguracionCors fuenteConfiguracion(PropiedadesCors props) {
        // Crear fuente de configuración basada en propiedades
    }
}

Mapeo de Propiedades

Las propiedades de configuración se enlazan a objetos:


@ConfigurationProperties(prefix = "spring.mvc.cors")
public class PropiedadesCors {
    private boolean habilitado = false;
    private String[] origenesPermitidos = new String[0];
    private String[] metodosPermitidos = new String[] {"GET", "POST"};
    private String[] cabecerasPermitidas = new String[0];
    private String[] cabecerasExpuestas = new String[0];
    private Boolean permitirCredenciales;
    private Long tiempoMaximo;
    
    // métodos get y set
}

Mecanismo de Registro del Filtro

El filtro se construye a partir de la configuración:


public class ConfiguracionCorsAuto {
    
    @Bean
    public FuenteConfiguracionCors fuenteConfiguracion(PropiedadesCors propiedades) {
        FuenteConfigURIBaseada origen = new FuenteConfigURIBaseada();
        ConfiguracionCors config = new ConfiguracionCors();
        
        config.setOrigenesPermitidos(Arrays.asList(propiedades.getOrigenesPermitidos()));
        config.setMetodosPermitidos(Arrays.asList(propiedades.getMetodosPermitidos()));
        config.setCabecerasPermitidas(Arrays.asList(propiedades.getCabecerasPermitidas()));
        config.setCabecerasExpuestas(Arrays.asList(propiedades.getCabecerasExpuestas()));
        
        if (propiedades.getTiempoMaximo() != null) {
            config.setTiempoMaximo(propiedades.getTiempoMaximo());
        }
        if (propiedades.getPermitirCredenciales() != null) {
            config.setPermitirCredenciales(propiedades.getPermitirCredenciales());
        }
        
        origen.registrarConfiguracion("/**", config);
        return origen;
    }
}

La integración en el contenedor Servlet se realiza mediante un bean de registro:


@Bean
public FilterRegistrationBean<filtrocors> registroFiltroCors(FuenteConfiguracionCors fuente) {
    FilterRegistrationBean<filtrocors> registro = new FilterRegistrationBean<>();
    registro.setFilter(new FiltroCORS(fuente));
    registro.setOrder(Ordered.HIGHEST_PRECEDENCE);
    registro.setName("filtroCors");
    return registro;
}
</filtrocors></filtrocors>

Flujo de Procesamiento de Solicitudes

El filtro gestiona solicitudes CORS y preflight:


public class FiltroCORS implements Filter {
    
    @Override
    public void doFilter(ServletRequest peticion, ServletResponse respuesta,
                       FilterChain cadena) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) peticion;
        HttpServletResponse res = (HttpServletResponse) respuesta;
        
        ConfiguracionCors config = this.fuenteConfig.obtenerConfiguracion(req);
        boolean valido = this.estrategia.gestionarSolicitud(config, req, res);
        
        if (!valido || UtilidadesCors.esSolicitudPreflight(req)) {
            return;
        }
        
        cadena.doFilter(req, res);
    }
}

La estrategia predeterminada verifica condiciones y maneja preflight:


public class EstrategiaCorsDefault implements EstrategiaCors {
    
    @Override
    public boolean gestionarSolicitud(ConfiguracionCors config,
                                  HttpServletRequest peticion,
                                  HttpServletResponse respuesta) throws IOException {
        if (!UtilidadesCors.esSolicitudCors(peticion)) {
            return true;
        }
        
        if (respuesta.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) != null) {
            return true;
        }
        
        boolean preflight = UtilidadesCors.esSolicitudPreflight(peticion);
        if (config == null) {
            if (preflight) {
                rechazarSolicitud(respuesta);
                return false;
            }
            return true;
        }
        
        return procesarInterno(config, peticion, respuesta, preflight);
    }
}

Mecanismo de Mapeo de Configuración

La conversión de propiedades a configuraciones se encapsula:


public class ConfiguracionCors {
    
    public static ConfiguracionCors convertir(PropiedadesCors propiedades) {
        ConfiguracionCors config = new ConfiguracionCors();
        
        if (propiedades.getOrigenesPermitidos() != null) {
            config.setOrigenesPermitidos(Arrays.asList(propiedades.getOrigenesPermitidos()));
        }
        if (propiedades.getMetodosPermitidos() != null) {
            config.setMetodosPermitidos(Arrays.asList(propiedades.getMetodosPermitidos()));
        }
        if (propiedades.getCabecerasPermitidas() != null) {
            config.setCabecerasPermitidas(Arrays.asList(propiedades.getCabecerasPermitidas()));
        }
        if (propiedades.getCabecerasExpuestas() != null) {
            config.setCabecerasExpuestas(Arrays.asList(propiedades.getCabecerasExpuestas()));
        }
        if (propiedades.getTiempoMaximo() != null) {
            config.setTiempoMaximo(propiedades.getTiempoMaximo());
        }
        if (propiedades.getPermitirCredenciales() != null) {
            config.setPermitirCredenciales(propiedades.getPermitirCredenciales());
        }
        
        return config;
    }
}

La fuente de configuración basada en URI soporta múltiples patrones:


public class FuenteConfigURIBaseada implements FuenteConfiguracionCors {
    
    public void registrarConfiguracion(String ruta, ConfiguracionCors config) {
        this.configuraciones.put(ruta, config);
    }
    
    @Override
    public ConfiguracionCors obtenerConfiguracion(HttpServletRequest peticion) {
        String rutaConsulta = this.asistenteRutas.obtenerRutaParaPeticion(peticion);
        for (Map.Entry<string configuracioncors=""> entrada : this.configuraciones.entrySet()) {
            if (this.comparadorRutas.coincide(entrada.getKey(), rutaConsulta)) {
                return entrada.getValue();
            }
        }
        return null;
    }
}
</string>

Integración con Spring Security

La configuración de seguridad puede incluir CORS:


@Configuration
@ConditionalOnClass(EnableWebSecurity.class)
public class ConfiguracionSeguridadCors extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().configurationSource(fuenteConfiguracion());
    }
    
    FuenteConfiguracionCors fuenteConfiguracion() {
        ConfiguracionCors configuracion = new ConfiguracionCors();
        configuracion.setOrigenesPermitidos(Arrays.asList("*"));
        configuracion.setMetodosPermitidos(Arrays.asList("GET", "POST"));
        FuenteConfigURIBaseada origen = new FuenteConfigURIBaseada();
        origen.registrarConfiguracion("/**", configuracion);
        return origen;
    }
}

Extensiones de Configuración Personalizada

Se pueden definir filtros CORS completamente personalizados:


@Configuration
public class ConfiguracionCorsPersonalizada {
    
    @Bean
    public FiltroCORS filtroCorsPersonalizado() {
        FuenteConfigURIBaseada origen = new FuenteConfigURIBaseada();
        ConfiguracionCors config = new ConfiguracionCors();
        
        config.setPermitirCredenciales(true);
        config.agregarOrigenPermitido("https://dominio.com");
        config.agregarCabeceraPermitida("*");
        config.agregarMetodoPermitido("*");
        
        origen.registrarConfiguracion("/api/**", config);
        return new FiltroCORS(origen);
    }
}

Optimización de Rendimiento

Se puede implementar caché para fuentes de configuración:


public class FuenteConfigConCaché implements FuenteConfiguracionCors {
    
    private final FuenteConfiguracionCors delegado;
    private final Cache<string configuracioncors=""> caché;
    
    public FuenteConfigConCaché(FuenteConfiguracionCors delegado) {
        this.delegado = delegado;
        this.caché = Caffeine.newBuilder()
            .maximumSize(1000)
            .expirarDespuésDeEscritura(5, TimeUnit.MINUTES)
            .build();
    }
    
    @Override
    public ConfiguracionCors obtenerConfiguracion(HttpServletRequest peticion) {
        String clave = peticion.getRequestURI();
        return caché.get(clave, k -> delegado.obtenerConfiguracion(peticion));
    }
}
</string>

Depuración y Resolución de Problemas

Para depuración, se pueden habilitar registros detallados:


logging.level.org.springframework.web.filter.CorsFilter=DEBUG  
logging.level.org.springframework.web.cors=TRACE

Problemas comunes incluyen configuración no aplicada, fallos en preflight, y problemas con credenciales, que se resuelven verificando propiedades, cabeceras y ajustes de seguridad.

Prácticas Recomendadas

Se sugiere aplicar el principio de mínimo privilegio, diferenciar configuraciones por entorno, y optimizar el caché para mejorar rendimiento.

Etiquetas: Spring Boot CORS CorsFilter java Filtración

Publicado el 7-11 17:56