我有一个解决方案,似乎可以完全解决 spring-boot:1.1.4、spring-security:3.2.4 和 thymeleaf:2.1.3 的这个问题(尽管它有点像黑客)。
这是修改后的单元测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class AppControllersTest {
@Autowired
public WebApplicationContext context;
@Autowired
private FilterChainProxy springSecurityFilter;
private MockMvc mockMvc;
@Before
public void setup() {
assertNotNull(context);
assertNotNull(springSecurityFilter);
// Process mock annotations
MockitoAnnotations.initMocks(this);
// Setup Spring test in webapp-mode (same config as spring-boot)
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.addFilters(springSecurityFilter)
.build();
context.getServletContext().setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
}
...
这里的魔力是迫使WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
作为实际的 Web 应用程序上下文(我注入的)。
这允许实际的 sec: 属性起作用,但我尝试设置权限以便用户登录的第二个测试没有通过(看起来用户仍然是匿名的)。
UPDATE
缺少一些东西(我认为这是 Spring 安全性工作方式的一个差距),但幸运的是相当容易解决(尽管这有点像黑客)。有关该问题的更多详细信息,请参阅:Spring 测试与安全:如何模拟身份验证?
我需要添加一个为测试创建模拟会话的方法。该方法将设置安全性Principal
/Authentication
并强制SecurityContext
进入HttpSession
然后可以将其添加到测试请求中(请参阅下面的测试片段和NamedOAuthPrincipal
类示例)。
public MockHttpSession makeAuthSession(String username, String... roles) {
if (StringUtils.isEmpty(username)) {
username = "azeckoski";
}
MockHttpSession session = new MockHttpSession();
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
Collection<GrantedAuthority> authorities = new HashSet<>();
if (roles != null && roles.length > 0) {
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
}
//Authentication authToken = new UsernamePasswordAuthenticationToken("azeckoski", "password", authorities); // causes a NPE when it tries to access the Principal
Principal principal = new NamedOAuthPrincipal(username, authorities,
"key", "signature", "HMAC-SHA-1", "signaturebase", "token");
Authentication authToken = new UsernamePasswordAuthenticationToken(principal, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authToken);
return session;
}
类来创建Principal
(通过 ConsumerCredentials 提供 OAuth 支持)。如果您不使用 OAuth,那么您可以跳过 ConsumerCredentials 部分,只需实现主体(但您应该返回 GrantedAuthority 的集合)。
public static class NamedOAuthPrincipal extends ConsumerCredentials implements Principal {
public String name;
public Collection<GrantedAuthority> authorities;
public NamedOAuthPrincipal(String name, Collection<GrantedAuthority> authorities, String consumerKey, String signature, String signatureMethod, String signatureBaseString, String token) {
super(consumerKey, signature, signatureMethod, signatureBaseString, token);
this.name = name;
this.authorities = authorities;
}
@Override
public String getName() {
return name;
}
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
}
然后像这样修改测试(创建会话,然后将其设置在模拟请求上):
@Test
public void testLoadRootWithAuth() throws Exception {
// Test basic home controller request with a session and logged in user
MockHttpSession session = makeAuthSession("azeckoski", "ROLE_USER");
MvcResult result = this.mockMvc.perform(get("/").session(session))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
.andReturn();
String content = result.getResponse().getContentAsString();
assertNotNull(content);
assertTrue(content.contains("Hello Spring Boot"));
}