La cadena de filtros de seguridad Spring es un motor muy complejo y flexible.
Los filtros clave en la cadena son (en el orden)
- SecurityContextPersistenceFilter (restaura la autenticación de JSESSIONID)
- UsernamePasswordAuthenticationFilter (realiza la autenticación)
- ExceptionTranslationFilter (captura excepciones de seguridad de FilterSecurityInterceptor)
- FilterSecurityInterceptor (puede arrojar excepciones de autenticación y autorización)
Mirando la documentación actual de la versión estable 4.2.1 , sección 13.3 Orden de filtros , puede ver toda la organización de filtros de la cadena de filtros:
13.3 Ordenar filtros
El orden en que se definen los filtros en la cadena es muy importante. Independientemente de los filtros que esté utilizando, el orden debe ser el siguiente:
ChannelProcessingFilter , porque podría necesitar redirigir a un protocolo diferente
SecurityContextPersistenceFilter , por lo que se puede configurar un SecurityContext en SecurityContextHolder al comienzo de una solicitud web, y cualquier cambio en SecurityContext se puede copiar en HttpSession cuando finaliza la solicitud web (listo para usar con la próxima solicitud web)
ConcurrentSessionFilter , porque utiliza la funcionalidad SecurityContextHolder y necesita actualizar SessionRegistry para reflejar las solicitudes en curso del director
Mecanismos de procesamiento de autenticación -
UsernamePasswordAuthenticationFilter , CasAuthenticationFilter , BasicAuthenticationFilter ,
etc. - para que SecurityContextHolder se pueda modificar para que contenga un token de solicitud de autenticación válido
El SecurityContextHolderAwareRequestFilter , si lo está utilizando para instalar un HttpServletRequestWrapper consciente de Spring Security en su contenedor de servlet
El JaasApiIntegrationFilter , si un JaasAuthenticationToken está en el SecurityContextHolder esto va a procesar el FilterChain como el sujeto en el JaasAuthenticationToken
RememberMeAuthenticationFilter , de modo que si ningún mecanismo de procesamiento de autenticación anterior actualizó SecurityContextHolder y la solicitud presenta una cookie que permite que se realicen los servicios recordar, se colocará un objeto de autenticación recordado adecuado
AnonymousAuthenticationFilter , de modo que si ningún mecanismo de procesamiento de autenticación anterior actualizara SecurityContextHolder, se colocará un objeto de autenticación anónimo
ExceptionTranslationFilter , para detectar cualquier excepción de Spring Security para que se pueda devolver una respuesta de error HTTP o se pueda iniciar un AuthenticationEntryPoint apropiado
FilterSecurityInterceptor , para proteger los URI web y generar excepciones cuando se deniega el acceso
Ahora, intentaré continuar con sus preguntas una por una:
Estoy confundido sobre cómo se usan estos filtros. ¿Es que para el inicio de sesión proporcionado por la primavera, UsernamePasswordAuthenticationFilter solo se usa para / login, y los últimos filtros no? ¿El elemento de espacio de nombres de inicio de sesión configura automáticamente estos filtros? ¿Todas las solicitudes (autenticadas o no) llegan a FilterSecurityInterceptor para la URL sin inicio de sesión?
Una vez que esté configurando una <security-http>
sección, para cada una debe proporcionar al menos un mecanismo de autenticación. Este debe ser uno de los filtros que coinciden con el grupo 4 en la sección 13.3 Orden de filtros de la documentación de Spring Security que acabo de mencionar.
Esta es la seguridad mínima válida: elemento http que se puede configurar:
<security:http authentication-manager-ref="mainAuthenticationManager"
entry-point-ref="serviceAccessDeniedHandler">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>
Simplemente, estos filtros se configuran en el proxy de la cadena de filtros:
{
"1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
"2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
"3": "org.springframework.security.web.header.HeaderWriterFilter",
"4": "org.springframework.security.web.csrf.CsrfFilter",
"5": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
"6": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
"7": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
"8": "org.springframework.security.web.session.SessionManagementFilter",
"9": "org.springframework.security.web.access.ExceptionTranslationFilter",
"10": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
}
Nota: Los obtengo creando un RestController simple que @Autowires FilterChainProxy y devuelve su contenido:
@Autowired
private FilterChainProxy filterChainProxy;
@Override
@RequestMapping("/filterChain")
public @ResponseBody Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
return this.getSecurityFilterChainProxy();
}
public Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
Map<Integer, Map<Integer, String>> filterChains= new HashMap<Integer, Map<Integer, String>>();
int i = 1;
for(SecurityFilterChain secfc : this.filterChainProxy.getFilterChains()){
//filters.put(i++, secfc.getClass().getName());
Map<Integer, String> filters = new HashMap<Integer, String>();
int j = 1;
for(Filter filter : secfc.getFilters()){
filters.put(j++, filter.getClass().getName());
}
filterChains.put(i++, filters);
}
return filterChains;
}
Aquí podríamos ver que solo al declarar el <security:http>
elemento con una configuración mínima, se incluyen todos los filtros predeterminados, pero ninguno de ellos es del tipo Autenticación (4to grupo en la sección 13.3 Orden de filtros). Entonces, en realidad significa que con solo declarar el security:http
elemento, SecurityContextPersistenceFilter, ExceptionTranslationFilter y FilterSecurityInterceptor se configuran automáticamente.
De hecho, se debe configurar un mecanismo de procesamiento de autenticación, e incluso el procesamiento de beans de espacio de nombres de seguridad reclama eso, arrojando un error durante el inicio, pero se puede omitir agregando un atributo de entrada-punto-referencia en <http:security>
Si agrego un básico <form-login>
a la configuración, de esta manera:
<security:http authentication-manager-ref="mainAuthenticationManager">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
<security:form-login />
</security:http>
Ahora, el filterChain será así:
{
"1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
"2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
"3": "org.springframework.security.web.header.HeaderWriterFilter",
"4": "org.springframework.security.web.csrf.CsrfFilter",
"5": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter",
"6": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter",
"7": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
"8": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
"9": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
"10": "org.springframework.security.web.session.SessionManagementFilter",
"11": "org.springframework.security.web.access.ExceptionTranslationFilter",
"12": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
}
Ahora, estos dos filtros org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter y org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter se crean y configuran en FilterChainProxy.
Entonces, ahora, las preguntas:
¿Es que para el inicio de sesión proporcionado por la primavera, UsernamePasswordAuthenticationFilter solo se usa para / login, y los últimos filtros no?
Sí, se utiliza para intentar completar un mecanismo de procesamiento de inicio de sesión en caso de que la solicitud coincida con la URL de UsernamePasswordAuthenticationFilter. Esta url se puede configurar o incluso cambiar su comportamiento para que coincida con cada solicitud.
También podría tener más de un mecanismo de procesamiento de autenticación configurado en el mismo FilterchainProxy (como HttpBasic, CAS, etc.).
¿El elemento de espacio de nombres de inicio de sesión configura automáticamente estos filtros?
No, el elemento form-login configura UsernamePasswordAUthenticationFilter, y en caso de que no proporcione una URL de página de inicio de sesión, también configura org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, que termina en un inicio de sesión simple autogenerado página.
Los otros filtros se configuran automáticamente de forma predeterminada simplemente creando un <security:http>
elemento sin security:"none"
atributo.
¿Todas las solicitudes (autenticadas o no) llegan a FilterSecurityInterceptor para la URL sin inicio de sesión?
Cada solicitud debe llegar a ella, ya que es el elemento que se encarga de si la solicitud tiene los derechos para llegar a la URL solicitada. Pero algunos de los filtros procesados antes podrían detener el procesamiento de la cadena de filtros simplemente no llamando FilterChain.doFilter(request, response);
. Por ejemplo, un filtro CSRF podría detener el procesamiento de la cadena de filtros si la solicitud no tiene el parámetro csrf.
¿Qué sucede si deseo asegurar mi API REST con JWT-token, que se recupera del inicio de sesión? Debo configurar dos etiquetas http de configuración de espacio de nombres, ¿derechos? Otro para / iniciar sesión con UsernamePasswordAuthenticationFilter
, y otro para URL de REST, con personalizado JwtAuthenticationFilter
.
No, no estás obligado a hacerlo de esta manera. Puede declarar ambos UsernamePasswordAuthenticationFilter
y el JwtAuthenticationFilter
en el mismo elemento http, pero depende del comportamiento concreto de cada uno de estos filtros. Ambos enfoques son posibles, y cuál elegir finalmente depende de sus propias preferencias.
¿La configuración de dos elementos http crea dos springSecurityFitlerChains?
Sí, eso es verdad
¿UsernamePasswordAuthenticationFilter está desactivado por defecto, hasta que declare el inicio de sesión de formulario?
Sí, puedes verlo en los filtros generados en cada una de las configuraciones que publiqué
¿Cómo puedo reemplazar SecurityContextPersistenceFilter con uno, que obtendrá la autenticación del token JWT existente en lugar de JSESSIONID?
Puede evitar SecurityContextPersistenceFilter, simplemente configurando la estrategia de sesión en <http:element>
. Solo configúrelo así:
<security:http create-session="stateless" >
O, en este caso, podría sobrescribirlo con otro filtro, de esta manera dentro del <security:http>
elemento:
<security:http ...>
<security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>
</security:http>
<beans:bean id="myCustomFilter" class="com.xyz.myFilter" />
EDITAR:
Una pregunta sobre "También podría tener más de un mecanismo de procesamiento de autenticación configurado en el mismo FilterchainProxy". ¿Este último sobrescribirá la autenticación realizada por el primero, si declara múltiples filtros de autenticación (implementación Spring)? ¿Cómo se relaciona esto con tener múltiples proveedores de autenticación?
Esto finalmente depende de la implementación de cada filtro en sí, pero es cierto el hecho de que los últimos filtros de autenticación al menos pueden sobrescribir cualquier autenticación previa que eventualmente haya hecho los filtros anteriores.
Pero esto no necesariamente sucederá. Tengo algunos casos de producción en servicios REST seguros donde utilizo un tipo de token de autorización que se puede proporcionar como un encabezado Http o dentro del cuerpo de la solicitud. Así que configuro dos filtros que recuperan ese token, en un caso del encabezado Http y el otro del cuerpo de la solicitud de la propia solicitud de descanso. Es cierto el hecho de que si una solicitud http proporciona ese token de autenticación como encabezado Http y dentro del cuerpo de la solicitud, ambos filtros intentarán ejecutar el mecanismo de autenticación delegándolo al administrador, pero podría evitarse fácilmente simplemente verificando si la solicitud es ya autenticado justo al comienzo del doFilter()
método de cada filtro.
Tener más de un filtro de autenticación está relacionado con tener más de un proveedor de autenticación, pero no lo fuerce. En el caso que expuse antes, tengo dos filtros de autenticación, pero solo tengo un proveedor de autenticación, ya que ambos filtros crean el mismo tipo de objeto de autenticación, por lo que en ambos casos el administrador de autenticación lo delega al mismo proveedor.
Y frente a esto, yo también tengo un escenario en el que publico solo un UsernamePasswordAuthenticationFilter pero las credenciales del usuario pueden estar contenidas en DB o LDAP, por lo que tengo dos proveedores de soporte UsernamePasswordAuthenticationToken, y AuthenticationManager delega cualquier intento de autenticación del filtro a los proveedores secuencialmente para validar las credenciales.
Entonces, creo que está claro que ni la cantidad de filtros de autenticación determina la cantidad de proveedores de autenticación ni la cantidad de proveedores determina la cantidad de filtros.
Además, la documentación indica que SecurityContextPersistenceFilter es responsable de limpiar SecurityContext, que es importante debido a la agrupación de subprocesos. Si lo omito o proporciono una implementación personalizada, tengo que implementar la limpieza manualmente, ¿verdad? ¿Hay más trucos similares al personalizar la cadena?
No examiné cuidadosamente este filtro antes, pero después de su última pregunta, he estado revisando su implementación y, como generalmente en Spring, casi todo se puede configurar, extender o sobrescribir.
Los SecurityContextPersistenceFilter delegados en un SecurityContextRepository aplicación la búsqueda de los SecurityContext. Por defecto, se utiliza un HttpSessionSecurityContextRepository , pero esto podría cambiarse utilizando uno de los constructores del filtro. Por lo tanto, puede ser mejor escribir un SecurityContextRepository que se ajuste a sus necesidades y simplemente configurarlo en SecurityContextPersistenceFilter, confiando en su comportamiento comprobado en lugar de comenzar a hacer todo desde cero.