如何在Spring Security测试中通过WithSecurityContextFactory设置SecurityContext?

时间:2015-03-31 17:54:56

标签: spring-security

我正在使用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:我跳过其他上下文配置文件,因为它们似乎与问题无关。

提前致谢!

1 个答案:

答案 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() {
        ...
    }

    [...]

}

一些亮点:

  • 您不再需要提供TestExecutionListeners
  • 使用.apply(springSecurity())而不是手动添加spring安全过滤器链

这是有效的,因为Spring Security的测试支持,即apply(springSecurity())将覆盖springSecurityFilterChain使用的SecurityContextRepository,以首先尝试TestSecurityContextHolder。