我正在使用Spring 4.1.5和Spring Security 4.0.0.RELEASE。
我阅读http://spring.io/blog/2014/05/07/preview-spring-security-test-method-security(Rob Winch的好文章)并开发了我自己的WithSecurityContextFactory实现,以便能够测试我的Spring MVC控制器:
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
@Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
final User fakeUser = new User();
final SecurityUser principal = new SecurityUser(fakeUser);
final Authentication auth = new UsernamePasswordAuthenticationToken(principal, "password", HelpersTest.getAuthorities(customUser.faps()));
final SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(auth);
return context;
}
}
我的抽象资源测试类如下:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations =
{
"classpath:spring/mock-daos-and-scan-for-services.xml",
"classpath:security.xml",
"classpath:singletons.xml",
"classpath:controller-scan.xml",
"classpath:servlet.xml" })
@TestExecutionListeners(listeners=
{
ServletTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
WithSecurityContextTestExcecutionListener.class })
public abstract class AbstractResourceMockMvcTest {
@Autowired
private WebApplicationContext wac;
@Autowired
private Filter springSecurityFilterChain;
private MockMvc mockMvc;
[...]
@Before
public void setup() {
this.mockMvc =
MockMvcBuilders.webAppContextSetup(this.getWac())
.addFilters(springSecurityFilterChain)
.build();
}
[...]
}
然后,我的具体测试类继承自AbstractResourceTest(从上面开始),它在@ Test-enabled方法上使用以下注释:
@WithMockCustomUser(faps={"promotion_read"})
跟踪代码,我可以确认调用WithMockCustomUserSecurityContextFactory.createSecurityContext()并在SecurityContextHolder.setContext()中设置其返回值(通过TestSecurityContextHolder.setContext())。
到目前为止,非常好!
然而,在此过程的后期,SecurityContextPersistenceFilter.doFilter()调用SecurityContextHolder.setContext(),这会覆盖测试设置的上下文,并且我忘记了我准备的模拟安全上下文。
的security.xml:
<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-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd
"
>
<!-- HTTP security handling -->
<security:http use-expressions="true">
<security:logout logout-url="/j_spring_security_logout" invalidate-session="true" logout-success-url="/login.jsp?loggedout=true" />
<security:custom-filter before="FIRST" ref="multiTenantRequestFilter" />
<!-- make sure following page are not secured -->
<security:intercept-url pattern="/*/*/internal/**" access="hasIpAddress('127.0.0.1')" />
<!-- make sure everything else going through the security filter is secured -->
<security:intercept-url pattern="/resources/**" access="hasRole('ROLE_USER')" requires-channel="any" />
<!-- supporting basic authentication for unattended connections (web services) -->
<security:http-basic />
</security:http>
<!-- authentication strategy -->
<security:authentication-manager alias="authManager">
<security:authentication-provider user-service-ref="userSecurityService">
<security:password-encoder ref="passwordEncoder" />
</security:authentication-provider>
</security:authentication-manager>
<!-- custom filter to intercept the tenant name from the login form -->
<bean id="multiTenantRequestFilter" class="com.meicpg.ti.web.MultiTenantRequestFilter" />
</beans>
servlet.xml中:
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
"
>
<mvc:annotation-driven>
<!-- Content skipped for StackOverflow question -->
</mvc:annotation-driven>
<context:annotation-config />
<bean id="annotationExceptionResolver" class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"></bean>
<security:global-method-security pre-post-annotations="enabled"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
如何防止此安全上下文覆盖?我的security.xml是否包含一个我错过的明显缺陷?
PS:我跳过其他上下文配置文件,因为它们似乎与问题无关。
提前致谢!
答案 0 :(得分:2)
不幸的是,博客文章仅用于方法级别的安全性,并且没有完整的MockMvc设置说明(系列中的以下博客也是如此)。此外,博客实际上已过时(我已更新它们以反映读者应参考参考文档)。您可以在参考文献的Testing Section中找到更新的说明。
简而言之,请将您的代码更新为以下内容:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations =
{
"classpath:spring/mock-daos-and-scan-for-services.xml",
"classpath:security.xml",
"classpath:singletons.xml",
"classpath:controller-scan.xml",
"classpath:servlet.xml" })
public abstract class AbstractResourceMockMvcTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
[...]
@Before
public void setup() {
this.mockMvc =
MockMvcBuilders.webAppContextSetup(this.getWac())
.apply(springSecurity())
.build();
}
@Test
@WithMockCustomUser(faps={"promotion_read"})
public void myTest() {
...
}
[...]
}
一些亮点:
这是有效的,因为Spring Security的测试支持,即apply(springSecurity())
将覆盖springSecurityFilterChain使用的SecurityContextRepository,以首先尝试TestSecurityContextHolder。