Mi compañía ha estado evaluando Spring MVC para determinar si deberíamos usarlo en uno de nuestros próximos proyectos. Hasta ahora me encanta lo que he visto, y ahora estoy echando un vistazo al módulo Spring Security para determinar si es algo que podemos / debemos usar.
Nuestros requisitos de seguridad son bastante básicos; un usuario solo necesita poder proporcionar un nombre de usuario y una contraseña para poder acceder a ciertas partes del sitio (como para obtener información sobre su cuenta); y hay un puñado de páginas en el sitio (preguntas frecuentes, soporte, etc.) donde se debe dar acceso a un usuario anónimo.
En el prototipo que he estado creando, he estado almacenando un objeto "LoginCredentials" (que solo contiene nombre de usuario y contraseña) en Session para un usuario autenticado; algunos de los controladores verifican si este objeto está en sesión para obtener una referencia al nombre de usuario conectado, por ejemplo. Estoy buscando reemplazar esta lógica local con Spring Security, que tendría el beneficio de eliminar cualquier tipo de "¿cómo rastreamos a los usuarios registrados?" y "¿cómo autenticamos a los usuarios?" de mi controlador / código comercial.
Parece que Spring Security proporciona un objeto "contextual" (por subproceso) para poder acceder al nombre de usuario / información principal desde cualquier lugar de su aplicación ...
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
... que parece muy poco primaveral ya que este objeto es un singleton (global), en cierto modo.
Mi pregunta es esta: si esta es la forma estándar de acceder a la información sobre el usuario autenticado en Spring Security, ¿cuál es la forma aceptada de inyectar un objeto de autenticación en SecurityContext para que esté disponible para mis pruebas unitarias cuando las pruebas unitarias requieren un usuario autenticado?
¿Necesito conectar esto en el método de inicialización de cada caso de prueba?
protected void setUp() throws Exception {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
...
}
Esto parece demasiado detallado. hay una manera mas facil?
El SecurityContextHolder
objeto en sí parece muy poco parecido a Spring ...
Simplemente hágalo de la manera habitual y luego insértelo usando
SecurityContextHolder.setContext()
en su clase de prueba, por ejemplo:Controlador:
Prueba:
fuente
Authentication a
agregar esto en el controlador? Como puedo entender en cada método de invocación? ¿Está bien para "spring way" solo agregarlo, en lugar de inyectar?@BeforeEach
(JUnit5) o@Before
(JUnit 4). Bueno y sencillo.Sin responder la pregunta sobre cómo crear e inyectar objetos de autenticación, Spring Security 4.0 ofrece algunas alternativas bienvenidas cuando se trata de pruebas. La
@WithMockUser
anotación permite al desarrollador especificar un usuario simulado (con autoridades opcionales, nombre de usuario, contraseña y roles) de una manera clara:También existe la opción de usar
@WithUserDetails
para emular unaUserDetails
devolución deUserDetailsService
, por ejemplo,Se pueden encontrar más detalles en los capítulos @WithMockUser y @WithUserDetails en los documentos de referencia de Spring Security (de los cuales se copiaron los ejemplos anteriores)
fuente
Tiene mucha razón en preocuparse: las llamadas a métodos estáticos son particularmente problemáticas para las pruebas unitarias, ya que no puede burlarse fácilmente de sus dependencias. Lo que le voy a mostrar es cómo dejar que el contenedor Spring IoC haga el trabajo sucio por usted, dejándolo con un código limpio y comprobable. SecurityContextHolder es una clase de marco y, aunque puede estar bien que su código de seguridad de bajo nivel esté vinculado a él, es probable que desee exponer una interfaz más ordenada a sus componentes de la interfaz de usuario (es decir, controladores).
cliff.meyers mencionó una forma de evitarlo: cree su propio tipo "principal" e inyecte una instancia en los consumidores. La etiqueta Spring < aop: scoped-proxy /> introducida en 2.x combinada con una definición de bean de alcance de solicitud, y el soporte del método de fábrica puede ser el ticket para el código más legible.
Podría funcionar de la siguiente manera:
Nada complicado hasta ahora, ¿verdad? De hecho, probablemente ya tenías que hacer la mayor parte de esto. A continuación, en su contexto de bean, defina un bean con ámbito de solicitud para contener el principal:
Gracias a la magia de la etiqueta aop: scoped-proxy, se llamará al método estático getUserDetails cada vez que ingrese una nueva solicitud HTTP y cualquier referencia a la propiedad currentUser se resolverá correctamente. Ahora las pruebas unitarias se vuelven triviales:
¡Espero que esto ayude!
fuente
Personalmente, solo usaría Powermock junto con Mockito o Easymock para burlar el SecurityContextHolder.getSecurityContext () estático en su prueba de unidad / integración, por ejemplo
Es cierto que aquí hay bastante código de placa de caldera, es decir, simula un objeto de autenticación, simula un SecurityContext para devolver la autenticación y finalmente simula el SecurityContextHolder para obtener el SecurityContext, sin embargo es muy flexible y le permite realizar pruebas unitarias para escenarios como objetos de autenticación nulos etc. sin tener que cambiar su código (no prueba)
fuente
Usar una estática en este caso es la mejor manera de escribir código seguro.
Sí, las estadísticas son generalmente malas, generalmente, pero en este caso, la estática es lo que quieres. Dado que el contexto de seguridad asocia un Principal con el subproceso que se está ejecutando actualmente, el código más seguro accedería a la estática del subproceso lo más directamente posible. Ocultar el acceso detrás de una clase de contenedor que se inyecta proporciona al atacante más puntos para atacar. No necesitarían acceso al código (que les resultaría difícil cambiar si se firmara el jar), solo necesitan una forma de anular la configuración, que se puede hacer en tiempo de ejecución o deslizar algún XML en el classpath. Incluso el uso de la inyección de anotaciones sería reemplazable con XML externo. Tal XML podría inyectar al sistema en ejecución con un principal falso.
fuente
Yo mismo hice la misma pregunta aquí , y acabo de publicar una respuesta que encontré recientemente. La respuesta corta es: inyecte a
SecurityContext
, y consulteSecurityContextHolder
solo en su configuración de Spring para obtener elSecurityContext
fuente
General
Mientras tanto (desde la versión 3.2, en el año 2013, gracias a SEC-2298 ) la autenticación se puede inyectar en métodos MVC utilizando la anotación @AuthenticationPrincipal :
Pruebas
En su prueba unitaria, obviamente, puede llamar a este Método directamente. En las pruebas de integración que use
org.springframework.test.web.servlet.MockMvc
, puede usarorg.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user()
para inyectar al usuario de esta manera:Sin embargo, esto solo llenará directamente el SecurityContext. Si desea asegurarse de que el usuario se carga desde una sesión en su prueba, puede usar esto:
fuente
Echaría un vistazo a las clases de prueba abstractas de Spring y los objetos simulados de los que se habla aquí . Proporcionan una forma poderosa de cablear automáticamente sus objetos administrados por Spring, lo que facilita las pruebas de unidad e integración.
fuente
La autenticación es una propiedad de un subproceso en el entorno del servidor de la misma manera que es una propiedad de un proceso en el sistema operativo. Tener una instancia de bean para acceder a la información de autenticación sería inconveniente para la configuración y la sobrecarga del cableado sin ningún beneficio.
Con respecto a la autenticación de prueba, hay varias formas de facilitarle la vida. Mi favorito es hacer una anotación personalizada
@Authenticated
y un oyente de ejecución de prueba, que lo gestiona. BuscaDirtiesContextTestExecutionListener
inspiración.fuente
Después de mucho trabajo pude reproducir el comportamiento deseado. Había emulado el inicio de sesión a través de MockMvc. Es demasiado pesado para la mayoría de las pruebas unitarias, pero útil para las pruebas de integración.
Por supuesto, estoy dispuesto a ver esas nuevas características en Spring Security 4.0 que facilitarán nuestras pruebas.
fuente