当前位置: 首页 > 知识库问答 >
问题:

Spring测试与安全:如何模拟身份验证?

岑经纶
2023-03-14

我试图弄清楚如何进行单元测试,如果我的控制器的URL是正确的安全的。以防万一有人改变了周围的东西,不小心移除了安全设置。

我的controller方法如下所示:

@RequestMapping("/api/v1/resource/test") 
@Secured("ROLE_USER")
public @ResonseBody String test() {
    return "test";
}

我设置了一个WebTestEnvironment,如下所示:

import javax.annotation.Resource;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({ 
        "file:src/main/webapp/WEB-INF/spring/security.xml",
        "file:src/main/webapp/WEB-INF/spring/applicationContext.xml",
        "file:src/main/webapp/WEB-INF/spring/servlet-context.xml" })
public class WebappTestEnvironment2 {

    @Resource
    private FilterChainProxy springSecurityFilterChain;

    @Autowired
    @Qualifier("databaseUserService")
    protected UserDetailsService userDetailsService;

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    protected DataSource dataSource;

    protected MockMvc mockMvc;

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    protected UsernamePasswordAuthenticationToken getPrincipal(String username) {

        UserDetails user = this.userDetailsService.loadUserByUsername(username);

        UsernamePasswordAuthenticationToken authentication = 
                new UsernamePasswordAuthenticationToken(
                        user, 
                        user.getPassword(), 
                        user.getAuthorities());

        return authentication;
    }

    @Before
    public void setupMockMvc() throws NamingException {

        // setup mock MVC
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(this.wac)
                .addFilters(this.springSecurityFilterChain)
                .build();
    }
}
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;

import eu.ubicon.webapp.test.WebappTestEnvironment;

public class CopyOfClaimTest extends WebappTestEnvironment {

    @Test
    public void signedIn() throws Exception {

        UsernamePasswordAuthenticationToken principal = 
                this.getPrincipal("test1");

        SecurityContextHolder.getContext().setAuthentication(principal);        

        super.mockMvc
            .perform(
                    get("/api/v1/resource/test")
//                    .principal(principal)
                    .session(session))
            .andExpect(status().isOk());
    }

}
    null

然而,如果仔细观察,这只在不向URL发送实际请求时才有帮助,而只在功能级别上测试服务时才有帮助。在我的案例中,抛出了“拒绝访问”异常:

org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.1.RELEASE.jar:3.2.1.RELEASE]
        ...

以下两条日志消息值得注意,基本上是说没有用户通过身份验证,表明设置主体不起作用,或者它被覆盖。

14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List test.TestController.test(); target is of class [test.TestController]; Attributes: [ROLE_USER]
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS

共有1个答案

夹谷奇
2023-03-14

在搜索答案时,我找不到任何一个既简单又灵活的答案,然后我找到了Spring Security Reference,我意识到有接近完美的解决方案。AOP解决方案通常是最好的测试方案,而Spring为其提供了@WithMockUser@WithUserDetails@WithSecurityContext,在此工件中:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>4.2.2.RELEASE</version>
    <scope>test</scope>
</dependency>

在大多数情况下,@WithUserDetails收集了我所需要的灵活性和功能。

基本上,您只需要创建一个自定义userdetailsservice,其中包含您想要测试的所有可能的用户配置文件。例如:

@TestConfiguration
public class SpringSecurityWebAuxTestConfig {

    @Bean
    @Primary
    public UserDetailsService userDetailsService() {
        User basicUser = new UserImpl("Basic User", "user@company.com", "password");
        UserActive basicActiveUser = new UserActive(basicUser, Arrays.asList(
                new SimpleGrantedAuthority("ROLE_USER"),
                new SimpleGrantedAuthority("PERM_FOO_READ")
        ));

        User managerUser = new UserImpl("Manager User", "manager@company.com", "password");
        UserActive managerActiveUser = new UserActive(managerUser, Arrays.asList(
                new SimpleGrantedAuthority("ROLE_MANAGER"),
                new SimpleGrantedAuthority("PERM_FOO_READ"),
                new SimpleGrantedAuthority("PERM_FOO_WRITE"),
                new SimpleGrantedAuthority("PERM_FOO_MANAGE")
        ));

        return new InMemoryUserDetailsManager(Arrays.asList(
                basicActiveUser, managerActiveUser
        ));
    }
}

现在我们的用户已经准备好了,所以想象一下我们想要测试对这个控制器函数的访问控制:

@RestController
@RequestMapping("/foo")
public class FooController {

    @Secured("ROLE_MANAGER")
    @GetMapping("/salute")
    public String saluteYourManager(@AuthenticationPrincipal User activeUser)
    {
        return String.format("Hi %s. Foo salutes you!", activeUser.getUsername());
    }
}

这里有一个get映射到路由/foo/salute的函数,我们正在使用@securite注释测试基于角色的安全性,不过您也可以测试@preauthorize@postauthorize。让我们创建两个测试,一个用于检查有效用户是否可以看到这个敬礼响应,另一个用于检查它是否被禁止。

@RunWith(SpringRunner.class)
@SpringBootTest(
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = SpringSecurityWebAuxTestConfig.class
)
@AutoConfigureMockMvc
public class WebApplicationSecurityTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithUserDetails("manager@company.com")
    public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception
    {
        mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
                .accept(MediaType.ALL))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("manager@company.com")));
    }

    @Test
    @WithUserDetails("user@company.com")
    public void givenBasicUser_whenGetFooSalute_thenForbidden() throws Exception
    {
        mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
                .accept(MediaType.ALL))
                .andExpect(status().isForbidden());
    }
}

如您所见,我们导入了SpringSecurityWebAuxTestConfig以便为用户提供测试。每一个只需使用一个简单的注释就可以在相应的测试用例上使用,从而减少了代码和复杂性。

如您所见,@WithUserDetails具有大多数应用程序所需的所有灵活性。它允许您使用具有任何GrantedAuthority的自定义用户,如角色或权限。但是如果您只是使用角色,那么测试就会更加容易,并且可以避免构造自定义的userdetailsservice。在这种情况下,使用@WithMockUser指定用户、密码和角色的简单组合。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(
    factory = WithMockUserSecurityContextFactory.class
)
public @interface WithMockUser {
    String value() default "user";

    String username() default "";

    String[] roles() default {"USER"};

    String password() default "password";
}

注释为一个非常基本的用户定义默认值。在我们的示例中,我们测试的路由只要求经过身份验证的用户是一个manager,因此我们可以放弃使用SpringSecurityWebAuxTestConfig来执行此操作。

@Test
@WithMockUser(roles = "MANAGER")
public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception
{
    mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
            .accept(MediaType.ALL))
            .andExpect(status().isOk())
            .andExpect(content().string(containsString("user")));
}

请注意,现在我们得到的不是用户manager@company.com,而是@withmockuser提供的默认值:用户;但这并不重要,因为我们真正关心的是他的角色:role_manager

 类似资料:
  • 问题内容: 我试图弄清楚如何对我的控制器的URL进行适当安全的单元测试。以防万一有人更改周围的内容并意外删除安全设置。 我的控制器方法如下所示: 我像这样设置一个WebTestEnvironment: 在我的实际测试中,我尝试执行以下操作: JUnit如何测试@PreAuthorize批注及其由spring MVC控制器指定的spring EL? 但是,如果仔细观察,这仅在不向URL发送实际请求时

  • 我用Spring Security做了一个概念验证,以便使用PRE_AUTH_FILTER过滤器执行预认证。它工作正常,但我想知道如果这个过滤器不起作用,我是否可以重定向到登录页面,因为我得到HTTP 403。< br >我的意思是,如果初始请求的头中不包含SM_USER字段,我如何重定向到登录页面?我需要考虑这两种情况(当它包含字段- SM_USER -和不包含时),但我无法让它工作。有什么想法

  • 我正在尝试让JDBC身份验证与我的小辅助项目一起工作,从表面上看,它应该可以工作,但它没有。所有的配置都遵循贝娄。 如果我切换到具有相同用户名/密码的inMemory身份验证,它就会非常工作。 这是我记录输出时得到的结果:

  • 我正在测试Spring Security中的登录方法。我想获得200的地位,但来了401。

  • 我正在尝试通过连接到LDAP使用Spring Security进行我的第一个演示。 我使用的Sping版本是:3.1.0.RELEASE 以下是我的security-integration.xml: 然而,每当我部署我的战争时,我都会遇到这个例外: HTTP状态500 - 类型异常报告 消息 描述服务器遇到一个内部错误(),该错误阻止它完成此请求。 例外情况 javax.servlet。Servl