为了在控制器方法上对PreAuthorize注释的hasRole部分进行单元测试,我需要什么?
我的测试应该成功,因为登录用户只有两个角色中的一个,但是它失败并出现以下断言错误:
java.lang.AssertionError:Status
预计:401
实际:200
我在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...
}
我创建了以下abstract-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>
在我的单元测试中,我有这个:
@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;
}
}
答案 0 :(得分:25)
Spring Security 4提供comprehensive support与MockMvc集成。例如:
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"))
...
}
...
问题
问题是设置SecurityContextHolder在此实例中不起作用。原因是SecurityContextPersistenceFilter将使用SecurityContextRepository尝试从HttpServletRequest中找出SecurityContext(默认情况下它使用HttpSession)。它找到(或找不到)的SecurityContext将覆盖您在SecurityContextHolder上设置的SecurityContext。
解决方案
要确保请求已通过身份验证,您需要使用您正在利用的SecurityContextRepository关联SecurityContext。默认值为HttpSessionSecurityContextRepository。允许您模拟用户登录的示例方法如下:
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);
}
如何使用它的细节可能仍然有点模糊,因为您可能不知道如何访问MockMvc中的HttpServletRequest,但请继续阅读,因为有更好的解决方案。
让事情变得更轻松
如果您想更轻松地与MockMvc进行此安全相关的交互,可以参考gs-spring-security-3.2示例应用程序。在项目中,您将找到一些用于处理Spring Security和MockMvc的实用程序,名为SecurityRequestPostProcessors。要使用它们,您可以将之前提到的类复制到项目中。使用此实用程序将允许您编写类似这样的内容:
RequestBuilder request = get("/110")
.with(user(rob).roles("USER"));
mvc
.perform(request)
.andExpect(status().isUnAuthorized());
注意:无需在请求上设置主体,因为只要用户通过身份验证,Spring Security就会为您建立Principal。
您可以在SecurityTests中找到其他示例。 该项目还将协助MockMvc和Spring Security之间的其他集成(即在执行POST时使用CSRF令牌设置请求)。
默认不包含在内?
您可能会问为什么默认情况下不会包含此内容。答案是我们根本没有时间进行3.2时间表。示例中的所有代码都可以正常工作,但我们对命名约定以及它是如何集成以释放它的确不够自信。您可以跟踪计划与Spring Security 4.0.0.M1一起发布的SEC-2015。
<强>更新强>
您的MockMvc实例还需要包含springSecurityFilterChain。为此,您可以使用以下内容:
@Autowired
private Filter springSecurityFilterChain;
@Test
public void testValidUserWithInvalidRoleFails() throws Exception {
MockMvc mockMvc = standaloneSetup(myController)
.addFilters(springSecurityFilterChain)
.setViewResolvers(viewResolver())
.build();
...
要使@Autowired
生效,您需要确保包含在您的@ContextConfiguration
中生成springSecurityFilterChain的安全配置。对于您当前的设置,这意味着&#34; classpath:/spring/abstract-security-test.xml"应该包含安全配置的<http ..>
部分(以及所有依赖bean)。或者,您可以在@ContextConfiguration
中包含具有<http ..>
部分安全配置(以及所有从属bean)的第二个文件。
答案 1 :(得分:2)
刚刚在上面添加了Rob的解决方案,截至2014年12月20日,Rob的上述答案中的主分支上的SecurityRequestPostProcessors
课程中存在一个错误,导致无法分配角色来自人口。
快速解决方法是在roles(String... roles)
内部静态类UserRequestPostProcessor
的{{1}}方法中注释掉以下代码行(当前为第181行):
SecurityRequestPostProcessors
。
您需要注释掉局部变量,而不是成员变量。
或者,您可以在从方法返回之前插入此行:
// List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(roles.length);
如果我有足够的声誉,我会在评论中加上这个。
答案 2 :(得分:-2)
MockMvcBuilders.standaloneSetup
手动实例化一个MyController
(没有Spring,因此没有AOP)。因此,不会拦截PreAuthorize,并且跳过安全检查。
因此,您可以@Autowire您的控制器,并将其传递到MockMvcBuilders.standaloneSetup
,以模拟使用@MockBean
传递给控制器的任何服务(有时需要),以便该服务的每个实例都被Mock替换。 / p>