繁体   English   中英

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

[英]Integration test for authentication filter with Spring Boot

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

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

我让我的 Web 配置器适配器创建了一个身份验证管理器并声明了我的过滤器:

@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));

    }

}

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

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

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

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

@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 和存根的魔力给我留下了深刻的印象。 :)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM