secured preauthorize hasrole hasauthority examples example enableglobalmethodsecurity annotation java testing spring-security mocking

java - preauthorize - spring security roles annotation example



¿Cómo hago una prueba unitaria de seguridad de primavera @PreAuthorize(hasRole)? (2)

ACTUALIZAR

Spring Security 4 proporciona soporte integral para la integración con MockMvc. Por ejemplo:

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @WebAppConfiguration public class SecurityMockMvcTests { @Autowired private WebApplicationContext context; private MockMvc mvc; @Before public void setup() { mvc = MockMvcBuilders .webAppContextSetup(context) .apply(springSecurity()) .build(); } @Test public void withUserRequestPostProcessor() { mvc .perform(get("/admin").with(user("admin").roles("USER","ADMIN"))) ... } @WithMockUser(roles="ADMIN") @Test public void withMockUser() { mvc .perform(get("/admin")) ... } ...

El problema

El problema es que la configuración de SecurityContextHolder no funciona en esta instancia. El motivo es que SecurityContextPersistenceFilter usará SecurityContextRepository para intentar averiguar el SecurityContext de HttpServletRequest (de forma predeterminada usa HttpSession). El SecurityContext que encuentre (o no encuentre) anulará el SecurityContext que haya establecido en SecurityContextHolder.

La solución

Para asegurarse de que la solicitud esté autenticada, debe asociar su SecurityContext utilizando el SecurityContextRepository que está aprovechando. El valor predeterminado es HttpSessionSecurityContextRepository. A continuación se muestra un método de ejemplo que le permitirá simular el inicio de sesión de un usuario:

private SecurityContextRepository repository = new HttpSessionSecurityContextRepository(); private void login(SecurityContext securityContext, HttpServletRequest request) { HttpServletResponse response = new MockHttpServletResponse(); HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response); repository.loadContext(requestResponseHolder); request = requestResponseHolder.getRequest(); response = requestResponseHolder.getResponse(); repository.saveContext(securityContext, request, response); }

Los detalles sobre cómo usar esto pueden ser un poco imprecisos, ya que es posible que no sepa cómo acceder a HttpServletRequest en MockMvc, pero siga leyendo ya que hay una mejor solución.

Haciéndolo más fácil

Si desea facilitar esta y otras interacciones relacionadas con la seguridad con MockMvc, puede consultar la aplicación de ejemplo gs-spring-security-3.2. Dentro del proyecto, encontrará algunas utilidades para trabajar con Spring Security y MockMvc llamadas SecurityRequestPostProcessors . Para usarlos puedes copiar esa clase mencionada anteriormente en tu proyecto. El uso de esta utilidad te permitirá escribir algo como esto en su lugar:

RequestBuilder request = get("/110") .with(user(rob).roles("USER")); mvc .perform(request) .andExpect(status().isUnAuthorized());

NOTA : No es necesario establecer el principal en la solicitud, ya que Spring Security establece el Principal por usted siempre y cuando el usuario esté autenticado.

Puedes encontrar ejemplos adicionales en SecurityTests . Este proyecto también ayudará en otras integraciones entre MockMvc y Spring Security (es decir, la configuración de la solicitud con el token CSRF al realizar un POST).

¿No incluido por defecto?

Puede preguntar por qué esto no está incluido por defecto. La respuesta es que simplemente no tuvimos tiempo para la línea de tiempo 3.2. Todo el código en el ejemplo funcionará bien, pero no teníamos la suficiente confianza en las convenciones de nomenclatura y cómo se integró exactamente para lanzar esto. Puede realizar un seguimiento de SEC-2015 que está programado para salir con Spring Security 4.0.0.M1.

Actualizar

Su instancia de MockMvc también debe contener el springSecurityFilterChain. Para hacerlo, puedes usar lo siguiente:

@Autowired private Filter springSecurityFilterChain; @Test public void testValidUserWithInvalidRoleFails() throws Exception { MockMvc mockMvc = standaloneSetup(myController) .addFilters(springSecurityFilterChain) .setViewResolvers(viewResolver()) .build(); ...

Para que el @Autowired funcione, debe asegurarse de incluir su configuración de seguridad que hace que springSecurityFilterChain en su @ContextConfiguration . Para su configuración actual, esto significa que "classpath: /spring/abstract-security-test.xml" debe contener su parte <http ..> de su configuración de seguridad (y todos los beans dependientes). Alternativamente, puede incluir un segundo archivo (s) en @ContextConfiguration que tiene su parte <http ..> de su configuración de seguridad (y todos los beans dependientes).

¿Qué necesito para realizar una prueba unitaria de la parte hasRole de una anotación de autorización previa en un método de controlador?

Mi prueba debería tener éxito porque el usuario que inició sesión solo tiene uno de los dos roles, pero en su lugar falla con el siguiente error de aserción:

java.lang.AssertionError: Estado

Esperado: 401

Actual: 200

Tengo el siguiente método en MyController:

@PreAuthorize(value = "hasRole(''MY_ROLE'') and hasRole(''MY_SECOND_ROLE'')") @RequestMapping(value = "/myurl", method = RequestMethod.GET) public String loadPage(Model model, Authentication authentication, HttpSession session) { ...stuff to do... }

Creé el siguiente resumen-security-test.xml:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:security="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd"> <security:global-method-security secured-annotations="enabled" /> <security:authentication-manager alias="authManager"> <security:authentication-provider> <security:user-service> <security:user name="missingsecondrole" password="user" authorities="MY_ROLE" /> </security:user-service> </security:authentication-provider> </security:authentication-manager> </beans>

Y en mi prueba de unidad tengo esto:

@ContextConfiguration("classpath:/spring/abstract-security-test.xml") public class MyTest { private final MyController myController = new MyController(); @Autowired private AuthenticationManager manager; @Test public void testValidUserWithInvalidRoleFails() throws Exception { MockMvc mockMvc = standaloneSetup(myController).setViewResolvers(viewResolver()).build(); Authentication auth = login("missingsecondrole", "user"); mockMvc.perform(get("/myurl") .session(session) .flashAttr(MODEL_ATTRIBUTE_NAME, new ModelMap()) .principal(auth)).andExpect(status().isUnauthorized()); } protected Authentication login(String name, String password) { Authentication auth = new UsernamePasswordAuthenticationToken(name, password); SecurityContextHolder.getContext().setAuthentication(manager.authenticate(auth)); return auth; } private ViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("WEB-INF/views"); viewResolver.setSuffix(".jsp"); return viewResolver; } }


Solo para agregarlo a la solución de Rob anterior, a partir del 20 de diciembre de 2014, hay un error en la clase SecurityRequestPostProcessors en la rama maestra de la respuesta de Rob anterior que impide que se llenen los roles asignados.

Una solución rápida es comentar la siguiente línea de código (actualmente línea 181) en el método de roles(String... roles) de la clase estática interna UserRequestPostProcessor de SecurityRequestPostProcessors :

// List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(roles.length); .

Debe comentar la variable local, NO la variable miembro.

Alternativamente, puede insertar esta línea justo antes de regresar del método:

this.authorities = authorities;

PD: Habría agregado esto como un comentario si hubiera tenido suficiente reputación.