unit-testing - 使用 Spring Boot 进行身份验证过滤器的集成测试

标签 unit-testing spring-security spring-boot mocking

我想实现一个集成测试来测试我的身份验证过滤器,用 Spring Security 和 Spring Boot 实现。但是……我迷路了……

首先,这是我的“生产”实现:

我有我的网络配置器适配器创建一个身份验证管理器并声明我的过滤器:

@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Autowired
    private IdentityService loginService;
    @Autowired
    private PersonService personService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(loginService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers(PATH_LOGIN).permitAll();
        http.authorizeRequests().antMatchers("/**").fullyAuthenticated();

        http.addFilterBefore(new AuthenticationFilter(PATH_LOGIN, authenticationManager(), personService),
            UsernamePasswordAuthenticationFilter.class);
    }

然后,这是我的过滤器实现:
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private ObjectMapper objectMapper = new ObjectMapper();

    private PersonService personService;

    protected AuthenticationFilter(String loginPath, AuthenticationManager authenticationManager,
        PersonService personService) {
        super(loginPath);
        this.personService = personService;
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException, IOException, ServletException {

        LoginInfo loginInfo = objectMapper.readValue(request.getInputStream(), LoginInfo.class);
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
            loginInfo.getUsername(), loginInfo.getPassword());

        Authentication authentication = getAuthenticationManager().authenticate(usernamePasswordAuthenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        return authentication;

    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
        Authentication authResult) throws IOException, ServletException {
        Identity identity = (Identity) authResult.getPrincipal();

        Person person = personService.getPersonByMail(identity.getUsername());

        UserInfo userInfos = new UserInfo();
        userInfos.setUser(person);
        userInfos.setRoles(identity.getRoles());

        objectMapper.writeValue(response.getWriter(), userInfos);
    }
}

现在,我已经实现了两个服务(PersonService 和 IdentityService),它们应该用作模拟来防止任何数据库访问:
@Profile("test")
@Service
public class PersonServiceMock implements PersonService {

    private static final Map<String, Person> USER_DB;

    static {
        Person valerian = new Student();
        valerian.setMail("valerian@savetheuniverse.com");

        USER_DB = new HashMap<>();
        USER_DB.put(valerian.getMail(), valerian);
    }

    @Override
    public Person getPersonByMail(String mail) {
        return USER_DB.get(mail);
    }

}

-
@Profile("test")
@Service
public class IdentityServiceMock implements IdentityService {

    private static final Map<String, Identity> USER_DB;

    static {
        Identity valerian = new Identity("valerian@savetheuniverse.com");

        USER_DB = new HashMap<>();
        USER_DB.put(valerian.getUsername(), valerian);

        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

        USER_DB.forEach((key, value) -> {
            value.setEnabled(true);
            value.setLocked(false);
            value.setPassword(encoder.encode("pa$$w0rd"));
        });
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDetails ud = USER_DB.get(username);
        return ud;
    }
}

最后,这是我写的“测试开始”,但这不起作用,因为它似乎想检索服务的“生产”实现而不是我的假实现:
@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class AuthenticationTests {

    @Autowired
    private Filter filterChainProxy;

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void before() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(filterChainProxy).build();
}

    @Test
    public void login() throws Exception {

        ObjectMapper objectMapper = new ObjectMapper();
        LoginInfo loginInfo = new LoginInfo("valerian@savetheworld.com", "pa$$w0rd");

        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/login")
            .content(objectMapper.writeValueAsString(loginInfo));

        Person person = new Student("valerian", "none", "valerian@savetheworld.com");
        UserInfo expectedUserInfo = new UserInfo(person, null);

        String expectedJSonContent = objectMapper.writeValueAsString(expectedUserInfo);

        mockMvc.perform(requestBuilder).andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.content().json(expectedJSonContent));

    }

}

我是不是误会了什么?你能帮我吗?

最佳答案

好的。没关系。只是我误解了一些概念,比如模拟、伪造和 stub ,即使模拟和 stub 在单元/集成测试中明确相关。

我修改了我的代码以删除不同的接口(interface)和服务的“模拟”实现。这种类型的实现更像是“假行为”实现而不是模拟。

最后,我的测试课有这个:

@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class AuthenticationTests {

    private static final String KNOWN_USER_MAIL = "valerian@mail.com";
    private static final String KNOWN_USER_PASSWORD = "pa$$w0rd";

    private static Person KNOWN_STUDENT = new Student("valerian", "none", KNOWN_USER_MAIL);
    private static Identity KNWON_IDENTITY = new Identity(KNOWN_USER_MAIL);

    static {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

        KNWON_IDENTITY.setEnabled(true);
        KNWON_IDENTITY.setLocked(false);
        KNWON_IDENTITY.setPassword(encoder.encode(KNOWN_USER_PASSWORD));
    }

    @Autowired
    // Attribute name very important
    private Filter springSecurityFilterChain;

    @Autowired
    private WebApplicationContext context;

    @MockBean // IdentityService automatically mocked when used
    private IdentityService identityService;

    @MockBean // PersonService automatically mocked when used
    private PersonService personService;

    private MockMvc mockMvc;

    @Before
    public void before() {

        mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(springSecurityFilterChain).build();

        // Stub to define the behaviour of the services when they are used
        Mockito.when(identityService.loadUserByUsername(KNOWN_USER_MAIL)).thenReturn(KNWON_IDENTITY);
        Mockito.when(personService.getPersonByMail(KNOWN_USER_MAIL)).thenReturn(KNOWN_STUDENT);
    }

    @Test
    public void login_success() throws Exception {

        ObjectMapper objectMapper = new ObjectMapper();
        LoginInfo loginInfo = new LoginInfo(KNOWN_USER_MAIL, KNOWN_USER_PASSWORD);

        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/login")
                .content(objectMapper.writeValueAsString(loginInfo));

        UserInfo expectedUserInfo = new UserInfo(KNOWN_STUDENT, KNWON_IDENTITY.getRoles());

        String expectedJSonContent = objectMapper.writeValueAsString(expectedUserInfo);
        mockMvc.perform(requestBuilder).andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().json(expectedJSonContent));

   }

}

注释@MockBean 和 stub 的魔力给我留下了深刻的印象。 :)

关于unit-testing - 使用 Spring Boot 进行身份验证过滤器的集成测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40071707/

相关文章:

spring - Grails上传文件异常,由springsecurity引起

java - Spring Boot找不到EmbeddedKafkaBroker Bean

c# - 如何测试在被测类中调用的方法?

java - 模拟 DAO 类及其中的方法

c# - 在 C# 中使用 Assert.AreEqual() 比较字符串时何时传递 Culture

tomcat - 浏览器 session 超时

java - 如何修复 java.io.InvalidClassException : org. springframework.security.core.context.SecurityContextImpl

unit-testing - 在 Vue Test Utils 中模拟退格键

spring-boot - 如何实现自定义 CompositeHealthContributor

java - 启动时的 Liquibase 迁移无法使用 Spring-Boot