Esta sección analiza la arquitectura de alto nivel de Spring Security en aplicaciones basadas en servlets.

Una breve descripción de los filtros

El soporte de servlet de Spring Security se basa en instancias de servlet Filter, por lo que es útil observar primero la función de Filter en general. La siguiente figura muestra una estructura en capas típica de controladores para una única solicitud HTTP.

El cliente envía una solicitud a la aplicación y el contenedor crea un FilterChain, que contiene instancias de un Filter y un Servlet que deben procesar un HttpServletRequest según la ruta del URI de solicitud. En una aplicación Spring MVC, Servlet es una instancia de DispatcherServlet. Sólo un Servlet puede manejar una HttpServletRequest y una HttpServletResponse. Sin embargo, puede utilizar más de un filtro para:

  • evitar llamadas al filtro o al descendente servlet. En este caso, Filter normalmente registra HttpServletResponse.

  • Cambios en HttpServletRequest o HttpServletResponse utilizado por los subsiguientes Filter y Servlet

El poder de Filter depende de FilterChain, que se le pasa.

Ejemplo de uso de FilterChain
Java

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // hacer algo antes de ejecutar el resto de la cadena de aplicaciones
    chain.doFilter(request, response); // invocar el resto de la aplicación
    // hacer algo después de ejecutar el resto de la aplicación
}
Kotlin

fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
    // hacer algo antes de ejecutar el resto de la aplicación
    chain.doFilter(request, response); // invocar el resto de la aplicación
    // hacer algo después el resto de la aplicación se ha ejecutado
}

Porque el Filtro solo afecta al subyacente Filtro y Servlet, el orden en el que se llama a cada Filter es extremadamente importante.

DelegatingFilterProxy

Spring proporciona un implementación de Filter titulado DelegatingFilterProxy, que le permite establecer un puente entre el ciclo de vida de un contenedor de servlets y ApplicationContext de Spring. El contenedor de servlets le permite registrar instancias Filter utilizando sus propios estándares, pero no conoce los beans definidos por Spring. DelegatingFilterProxy se puede registrar a través de mecanismos de contenedor de servlets estándar, pero delega todo el trabajo al Spring Bean que implementa Filter.

Así es como DelegatingFilterProxy se utiliza encaja en un esquema con instancias Filter y FilterChain.

DelegatingFilterProxy busca Bean Filter0 en ApplicationContext y luego llama a Bean Filter0. El pseudocódigo para DelegatingFilterProxy se muestra a continuación.

Pseudocódigo para DelegatingFilterProxy
Java

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // En modo diferido, obtenemos el filtro que se registró como Spring bean
    // Para el ejemplo en DelegatingFilterProxy, el delegado es la instancia Bean Filter0
    Filter delegate = getFilterBean(someBeanName);
    // delega el trabajo al Spring Bean delegado.doFilter(solicitud, respuesta);
}
Kotlin

fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
    // En modo diferido, obtenemos el filtro que se registró como Spring Bean
    // Para el ejemplo en DelegatingFilterProxy, el delegado es una instancia de Bean Filter0
    val delegate: Filter = getFilterBean(someBeanName)
    // delegar el trabajo con Spring Bean delegado.doFilter(solicitud, respuesta)
}

Otra ventaja de DelegatingFilterProxy es que le permite diferir la búsqueda de instancias de beans Filter. Esto es importante porque el contenedor necesita registrar instancias Filter antes de ejecutarlo. Sin embargo, Spring normalmente usa ContextLoaderListener para cargar Spring beans, lo que solo ocurre después de que se hayan registrado las instancias Filter.

FilterChainProxy

Las instalaciones de servlet en Spring Security están contenidas en FilterChainProxy. FilterChainProxy es un Filter especializado proporcionado por Spring Security que le permite delegar autoridad a múltiples instancias de Filter a través de SecurityFilterChain. Dado que FilterChainProxy es un bean, generalmente está incluido en DelegatingFilterProxy.

SecurityFilterChain

SecurityFilterChain es utilizado por FilterChainProxy para determinar qué Filter de Spring Security debe llamarse para una solicitud determinada.

Los filtros de seguridad en SecurityFilterChain suelen ser beans, pero están registrados en FilterChainProxy en lugar de DelegatingFilterProxy. FilterChainProxy proporciona varias ventajas sobre el registro directo con el contenedor de servlets o DelegatingFilterProxy. Primero, proporciona el punto de partida para todo el soporte de servlets en Spring Security. Por este motivo, si está intentando solucionar problemas de compatibilidad con servlets de Spring Security, un buen lugar para comenzar es agregar un punto de depuración a FilterChainProxy.

En segundo lugar, porque FilterChainProxy es fundamental para el uso de Spring Security, puede realizar tareas que no se consideran opcionales. Por ejemplo, limpia SecurityContext para evitar pérdidas de memoria. También utiliza HttpFirewall de Spring Security para proteger las aplicaciones de ciertos tipos de ataques.

También proporciona mayor flexibilidad para determinar cuándo llamar a SecurityFilterChain. En un contenedor de servlets, las instancias Filter se llaman basándose únicamente en la URL. Sin embargo, FilterChainProxy puede determinar la llamada basándose en cualquier cosa en HttpServletRequest usando la interfaz RequestMatcher.

De hecho, FilterChainProxy se puede utilizar para determinar qué SecurityFilterChain se debe utilizar. Esto le permite proporcionar una configuración completamente separada para diferentes partes de su aplicación.

En la imagen con varios SecurityFilterChain, el FilterChainProxy La instancia decide qué SecurityFilterChain debe usarse. Sólo se llamará al primer SecurityFilterChain coincidente. Si se solicita la URL /api/messages/, primero se comparará con el patrón SecurityFilterChain0 a través de /api/* *, por lo que solo se llamará SecurityFilterChain0, incluso si también coincide con SecurityFilterChainn. Si se solicita la URL /messages/, no se comparará con el patrón SecurityFilterChain0 a través de /api/**, por lo que FilterChainProxy continuará iterando sobre cada SecurityFilterChain. Se supone que no se llamarán otras instancias de SecurityFilterChain que coincidan con SecurityFilterChainn.

Tenga en cuenta que en SecurityFilterChain0 solo se configuran tres instancias de Filter. Sin embargo, hay cuatro Filter configurados en SecurityFilterChainn. Es importante tener en cuenta que cada SecurityFilterChain puede ser única y configurarse de forma aislada. De hecho, SecurityFilterChain puede tener cero instancias de Filter si la aplicación quiere que Spring Security ignore ciertas solicitudes.

Filtros de seguridad

Los filtros Spring Security se agregan a FilterChainProxy a través de la API SecurityFilterChain. El orden de las instancias de Filter es importante. Normalmente no es necesario conocer el orden de Filter en Spring Security. Sin embargo, en algunos casos es útil saber en qué orden están.

La siguiente es una lista ordenada completa de los filtros Spring Security:

  • ForceEagerSessionCreationFilter

  • ChannelProcessingFilter

  • WebAsyncManagerIntegrationFilter

  • SecurityContextPersistenceFilter

  • HeaderWriterFilter

  • CorsFilter

  • CsrfFilter

  • LogoutFilter

  • OAuth2AuthorizationRequestRedirectFilter

  • Saml2WebSsoAuthenticationRequestFilter

  • X509AuthenticationFilter

  • AbstractPreAuthenticatedProcessingFilter

  • CasAuthenticationFilter

  • OAuth2LoginAuthenticationFilter

  • Saml2WebSsoAuthenticationFilter

  • Nombre de usuarioContraseñaAuthenticationFilter

  • OpenIDAuthenticationFilter

  • DefaultLoginPageGeneratingFilter

  • DefaultLogoutPageGeneratingFilter

  • ConcurrentSessionFilter

  • DigestAuthenticationFilter

  • BearerTokenAuthenticationFilter

  • BasicAuthenticationFilter

  • RequestCacheAwareFilter

  • SecurityContextHolderAwareRequestFilter

  • JaasApiIntegrationFilter

  • RememberMeAuthenticationFilter

  • AnonymousAuthenticationFilter

  • OAuth2AuthorizationCodeGrantFilter

  • SessionManagementFilter

  • ExceptionTranslationFilter

  • FilterSecurityInterceptor

  • SwitchUserFilter

Manejo de excepciones de seguridad

ExceptionTranslationFilter permite la conversión AccessDeniedException y AuthenticationException en las respuestas HTTP.

ExceptionTranslationFilter se agrega a FilterChainProxy en calidad de uno de los filtros de seguridad.

  • 1 El primer ExceptionTranslationFilter accede a FilterChain.doFilter(solicitud, respuesta) para llamar al resto de la aplicación.

  • 2 Si el usuario no está autenticado o se produce una AuthenticationException ocurre, luego ejecute procedimiento de autenticación.

    • TheSecurityContextHolder se borra

    • HttpServletRequest se guarda en el destino SolicitarCaché. Una vez que el usuario se haya autenticado correctamente, se utilizará RequestCache para reproducir la solicitud original.

    • AuthenticationEntryPoint se utilizará para solicitar credenciales de datos del cliente. Por ejemplo, podría redirigir a una página de inicio de sesión o enviar un encabezado WWW-Authenticate.

  • 3 De lo contrario, si se produce AccessDeniedException, se denegará el acceso. AccessDeniedHandler se llama para manejar la denegación de acceso.

Si la aplicación no genera una AccessDeniedException o una AuthenticationException, entonces el ExceptionTranslationFilter no hace nada.

El pseudocódigo para ExceptionTranslationFilter se parece a esto:

Pseudocódigo de ExceptionTranslationFilter
try {
    filterChain.doFilter(request, response); 
} catch (AccessDeniedException | AuthenticationException ex) {
    if (!authenticated || ex instanceof AuthenticationException) {
        startAuthentication();
    } else {
        accessDenied(); 
    }
}
  1. De la breve descripción de las instancias de Filter, puede recordar que llamar a FilterChain.doFilter(solicitud, respuesta) es equivalente a llamar al resto de la aplicación. significa que si otra parte de la aplicación (por ejemplo, FilterSecurityInterceptor o método de seguridad) genera una AuthenticationException o AccessDeniedException, entonces esas excepciones serán detectadas y manejadas. en esta etapa.
  2. Si el usuario no está autenticado o se genera una AuthenticationException, entonces ejecute el procedimiento de autenticación.
  3. De lo contrario se denegará el acceso