Estaba tratando de averiguar cómo realizar una prueba unitaria si las URL de mis controladores están protegidas correctamente. En caso de que alguien cambie las cosas y elimine accidentalmente la configuración de seguridad.
Mi método de controlador se ve así:
@RequestMapping("/api/v1/resource/test")
@Secured("ROLE_USER")
public @ResonseBody String test() {
return "test";
}
Configuré un WebTestEnvironment así:
import javax.annotation.Resource;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/security.xml",
"file:src/main/webapp/WEB-INF/spring/applicationContext.xml",
"file:src/main/webapp/WEB-INF/spring/servlet-context.xml" })
public class WebappTestEnvironment2 {
@Resource
private FilterChainProxy springSecurityFilterChain;
@Autowired
@Qualifier("databaseUserService")
protected UserDetailsService userDetailsService;
@Autowired
private WebApplicationContext wac;
@Autowired
protected DataSource dataSource;
protected MockMvc mockMvc;
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected UsernamePasswordAuthenticationToken getPrincipal(String username) {
UserDetails user = this.userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
user,
user.getPassword(),
user.getAuthorities());
return authentication;
}
@Before
public void setupMockMvc() throws NamingException {
// setup mock MVC
this.mockMvc = MockMvcBuilders
.webAppContextSetup(this.wac)
.addFilters(this.springSecurityFilterChain)
.build();
}
}
En mi prueba real intenté hacer algo como esto:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import eu.ubicon.webapp.test.WebappTestEnvironment;
public class CopyOfClaimTest extends WebappTestEnvironment {
@Test
public void signedIn() throws Exception {
UsernamePasswordAuthenticationToken principal =
this.getPrincipal("test1");
SecurityContextHolder.getContext().setAuthentication(principal);
super.mockMvc
.perform(
get("/api/v1/resource/test")
// .principal(principal)
.session(session))
.andExpect(status().isOk());
}
}
Recogí esto aquí:
- http://java.dzone.com/articles/spring-test-mvc-junit-testing aquí:
- http://techdive.in/solutions/how-mock-securitycontextholder-perfrom-junit-tests-spring-controller o aquí:
- ¿Cómo JUnit prueba una anotación @PreAuthorize y su resorte EL especificado por un controlador Spring MVC?
Sin embargo, si uno mira de cerca, esto solo ayuda cuando no se envían solicitudes reales a las URL, sino solo cuando se prueban los servicios a nivel de función. En mi caso, se lanzó una excepción de "acceso denegado":
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.1.RELEASE.jar:3.2.1.RELEASE]
...
Los siguientes dos mensajes de registro son dignos de mención, básicamente, que dicen que ningún usuario se autenticó, lo que indica que la configuración Principal
no funcionó o que se sobrescribió.
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List test.TestController.test(); target is of class [test.TestController]; Attributes: [ROLE_USER]
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
Respuestas:
Buscando una respuesta, no pude encontrar ninguna que fuera fácil y flexible al mismo tiempo, luego encontré Spring Security Reference y me di cuenta de que hay soluciones casi perfectas. Las soluciones de AOP a menudo son las mejores para probar, y Spring las proporciona
@WithMockUser
,@WithUserDetails
y@WithSecurityContext
en este artefacto:En la mayoría de los casos,
@WithUserDetails
reúne la flexibilidad y el poder que necesito.¿Cómo funciona @WithUserDetails?
Básicamente, solo necesita crear un
UserDetailsService
perfil personalizado con todos los posibles perfiles de usuarios que desea probar. P.ejAhora tenemos a nuestros usuarios listos, así que imagine que queremos probar el control de acceso a esta función del controlador:
Aquí tenemos una función de asignación a la ruta / foo / salute y estamos probando una seguridad basada en roles con la
@Secured
anotación, aunque también puede probar@PreAuthorize
y@PostAuthorize
. Creemos dos pruebas, una para verificar si un usuario válido puede ver esta respuesta de saludo y la otra para verificar si en realidad está prohibida.Como ve, lo importamos
SpringSecurityWebAuxTestConfig
para proporcionar a nuestros usuarios para que lo prueben. Cada uno usado en su caso de prueba correspondiente simplemente usando una anotación sencilla, reduciendo el código y la complejidad.Utilice mejor @WithMockUser para una seguridad basada en roles más sencilla
Como ve,
@WithUserDetails
tiene toda la flexibilidad que necesita para la mayoría de sus aplicaciones. Le permite utilizar usuarios personalizados con cualquier autoridad otorgada, como roles o permisos. Pero si solo está trabajando con roles, las pruebas pueden ser aún más fáciles y podría evitar la construcción de una personalizadaUserDetailsService
. En tales casos, especifique una combinación simple de usuario, contraseña y roles con @WithMockUser .La anotación define valores predeterminados para un usuario muy básico. Como en nuestro caso, la ruta que estamos probando solo requiere que el usuario autenticado sea un administrador, podemos dejar de usar
SpringSecurityWebAuxTestConfig
y hacer esto.Tenga en cuenta que ahora, en lugar del usuario [email protected], obtenemos el valor predeterminado proporcionado por
@WithMockUser
: usuario ; sin embargo, no importará, porque lo que realmente importa es su papel:ROLE_MANAGER
.Conclusiones
Como puede ver con anotaciones como
@WithUserDetails
y@WithMockUser
podemos cambiar entre diferentes escenarios de usuarios autenticados sin construir clases alienadas de nuestra arquitectura solo para hacer pruebas simples. También se recomienda que vea cómo funciona @WithSecurityContext para obtener aún más flexibilidad.fuente
tom
, mientras que la segunda es porjerry
?BasicUser
yManager User
dado en la respuesta. El concepto clave es que en lugar de preocuparnos por los usuarios, en realidad nos preocupamos por sus roles, pero cada una de esas pruebas, ubicadas en la misma prueba unitaria, en realidad representan consultas diferentes. realizado por diferentes usuarios (con diferentes roles) al mismo punto final.Desde Spring 4.0+, la mejor solución es anotar el método de prueba con @WithMockUser
Recuerde agregar la siguiente dependencia a su proyecto
fuente
Resultó que
SecurityContextPersistenceFilter
, que es parte de la cadena de filtros de Spring Security, siempre restablece miSecurityContext
, que configuré llamandoSecurityContextHolder.getContext().setAuthentication(principal)
(o usando el.principal(principal)
método). Este filtro establece elSecurityContext
en elSecurityContextHolder
con aSecurityContext
de unSecurityContextRepository
SOBRESCRIBIR el que configuré anteriormente. El repositorio es de formaHttpSessionSecurityContextRepository
predeterminada. ElHttpSessionSecurityContextRepository
inspecciona lo dadoHttpRequest
e intenta acceder al correspondienteHttpSession
. Si existe, intentará leer elSecurityContext
archivoHttpSession
. Si esto falla, el repositorio genera un archivoSecurityContext
.Por lo tanto, mi solución es pasar
HttpSession
junto con la solicitud, que contieneSecurityContext
:fuente
getPrincipal()
que, en mi opinión, es un poco engañoso. idealmente debería haber sido nombradogetAuthentication()
. del mismo modo, en susignedIn()
prueba, la variable local debe ser nombradaauth
o enauthentication
lugar deprincipal
Agregue pom.xml:
y uso
org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
para solicitud de autorización. Vea el uso de muestra en https://github.com/rwinch/spring-security-test-blog ( https://jira.spring.io/browse/SEC-2592 ).Actualizar:
4.0.0.RC2 funciona para Spring-Security 3.x. Para spring-security 4 spring-security-test conviértase en parte de spring-security ( http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test , la versión es la misma ).
La configuración ha cambiado: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-mockmvc
Ejemplo de autenticación básica: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#testing-http-basic-authentication .
fuente
Aquí hay un ejemplo para aquellos que desean probar Spring MockMvc Security Config utilizando la autenticación básica Base64.
Dependencia de Maven
fuente
Respuesta corta:
Después de realizar la
formLogin
prueba de seguridad de primavera, cada una de sus solicitudes se llamará automáticamente como usuario conectado.Respuesta larga:
Verifique esta solución (la respuesta es para Spring 4): Cómo iniciar sesión un usuario con Spring 3.2 New MVC testing
fuente
Opciones para evitar el uso de SecurityContextHolder en pruebas:
SecurityContextHolder
usando alguna biblioteca simulada - EasyMock por ejemploSecurityContextHolder.get...
en su código en algún servicio; por ejemplo,SecurityServiceImpl
con el métodogetCurrentPrincipal
que implementa laSecurityService
interfaz y luego, en sus pruebas, puede simplemente crear una implementación simulada de esta interfaz que devuelve el principal deseado sin acceso aSecurityContextHolder
.fuente