spring 删除过时的WebSecurityConfigurerAdapter后,WebMvcTest不工作

laik7k3q  于 2023-01-08  发布在  Spring
关注(0)|答案(1)|浏览(226)

升级我的应用程序的SecurityConfig,我得到了工作版本,但破坏了这么多的测试。
当前弃用版本为here,文件之间的差异如下所示
新版本的效果很好(我和 Postman 一起试过了)。
与差异版本我得到了每一个测试通过100%,现在我有几个错误,我不能解决自己。
例如,该测试:

@WebMvcTest(PingController.class)
class AuthTokenFilterTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    AuthTokenFilter authTokenFilter;

    @MockBean
    JwtUtils jwtUtils;

    @MockBean
    UserDetailsServiceImpl userDetailsServiceImpl;

    @MockBean
    UserDetails userDetails;

    @MockBean
    private PingService pingService;

    @MockBean
    AuthEntryPointJwt authEntryPointJwt;

    @MockBean
    UsersRepository usersRepository;

    @Test
    void testCanReturnNullIfJwtIsMissing() throws Exception {
        mvc.perform(get("/api/v1/ping")).andExpect(status().isOk());
    }

    /**
     * Test can validate the token.
     * 
     * Use /ping route because it is the route out of security, so we can
     * concentrate to the AuthTokenFilter class.
     * 
     * @throws Exception
     */
    @Test
    void testCanValidateToken() throws Exception {
        String token = "a1.b2.c3";

        when(jwtUtils.validateJwtToken(token)).thenReturn(true);
        when(jwtUtils.getUserNameFromJwtToken(token)).thenReturn("username");
        when(userDetailsServiceImpl.loadUserByUsername("username")).thenReturn(userDetails);
        when(userDetails.getAuthorities()).thenReturn(null);

        mvc.perform(get("/api/v1/ping").header("Authorization", "Bearer " + token)).andExpect(status().isOk());
    }

    /**
     * Test cannot validate the token.
     * 
     * Use /ping route because it is the route out of security, so we can
     * concentrate to the AuthTokenFilter class.
     * 
     * @throws Exception
     */
    @Test
    void testCannotValidateToken() throws Exception {
        String token = "a1.b2.c3";
        when(jwtUtils.validateJwtToken(token)).thenReturn(false);
        mvc.perform(get("/api/v1/ping").header("Authorization", "Bearer " + token)).andExpect(status().isOk());
    }

    /**
     * Test cannot validate the token if the header is missing.
     * 
     * Use /ping route because it is the route out of security, so we can
     * concentrate to the AuthTokenFilter class.
     * 
     * @throws Exception
     */
    @Test
    void testCanReturnNullIfJwtIsMissingButOtherHeaderIsInPlace() throws Exception {
        String token = "a1.b2.c3";
        mvc.perform(get("/api/v1/ping").header("Authorization", "NotStartWithBearer " + token))
                .andExpect(status().isOk());
    }

}

我得到
第一个月
(The测试为here
或者,在寄存器控制器测试中

@WebMvcTest(AuthController.class)
@TestPropertySource(locations = "classpath:application.properties", properties = "app.enableSubscription=false")
class RegisterControllerTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    AuthTokenFilter authTokenFilter;

    @MockBean
    AuthEntryPointJwt authEntryPointJwt;

    @MockBean
    JwtUtils jwtUtils;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    UserDetailsServiceImpl userDetailsServiceImpl;

    @MockBean
    AuthenticationManager authenticationManager;

    @MockBean
    Authentication authentication;

    @MockBean
    SecurityContext securityContext;

    @MockBean
    private RolesService rolesService;

    @Test
    @WithMockUser(username = "username", authorities = { "ROLE_ADMIN" })
    void cannotRegisterUserWhenSubscriptionsAreDisabled() throws Exception {

        var userToSave = validUserEntity("username", "password");
        var savedUser = validUserEntity("username", "a1.b2.c3");

        when(userDetailsServiceImpl.post(userToSave)).thenReturn(savedUser);

        mvc.perform(post("/api/v1/auth/register/").contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsBytes(userToSave))).andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.status", is("error")))
                .andExpect(jsonPath("$.message", is("subscriptions disabled")));
    }

    private static UsersEntity validUserEntity(String username, String password) {
        return UsersEntity.builder().username(username).password(password).build();
    }
}

我得到Error creating bean with name 'authController': Unsatisfied dependency expressed through field 'passwordEncoder'

安全配置

旧版本:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsServiceImpl userDetailsServiceImpl;

    @Autowired
    private AuthEntryPointJwt authEntryPointJwt;

    @Bean
    AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    @Value("${app.allowedOrigins}")
    private String allowedOriginsFromApplicationProperties;

    /**
     * Return allowedOrigins from application properties
     */
    private String getAllowedOriginsFromApplicationProperties() {
        return this.allowedOriginsFromApplicationProperties;
    }

    /**
     * Return the allowed origins.
     * 
     * @return
     */
    private List<String> getAllowedOrigins() {
        String[] allowedOrigins = this.getAllowedOriginsFromApplicationProperties().split(",");
        return Arrays.asList(allowedOrigins);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userDetailsServiceImpl).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(authEntryPointJwt).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
                .mvcMatchers("/api/v1/ping").permitAll().mvcMatchers("/api/v1/auth/register").permitAll()
                .mvcMatchers("/api/v1/auth/login").permitAll().mvcMatchers("/api-docs/**").permitAll()
                .mvcMatchers("/swagger-ui/**").permitAll().mvcMatchers("/swagger-ui.html").permitAll()
                .mvcMatchers("/documentation").permitAll().mvcMatchers("/").permitAll().anyRequest().authenticated();
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

    }

    @Bean
    CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.setAllowedOriginPatterns(getAllowedOrigins());
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

}

和新的

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfiguration {

    @Autowired
    UserDetailsServiceImpl userDetailsServiceImpl;

    @Autowired
    AuthEntryPointJwt authEntryPointJwt;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
            throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    @Value("${app.allowedOrigins}")
    private String allowedOriginsFromApplicationProperties;

    /**
     * Return allowedOrigins from application properties
     */
    private String getAllowedOriginsFromApplicationProperties() {
        return this.allowedOriginsFromApplicationProperties;
    }

    /**
     * Return the allowed origins.
     * 
     * @return
     */
    private List<String> getAllowedOrigins() {
        String[] allowedOrigins = this.getAllowedOriginsFromApplicationProperties().split(",");
        return Arrays.asList(allowedOrigins);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(authEntryPointJwt).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
                .mvcMatchers("/api/v1/ping").permitAll().mvcMatchers("/api/v1/auth/register").permitAll()
                .mvcMatchers("/api/v1/auth/login").permitAll().mvcMatchers("/api-docs/**").permitAll()
                .mvcMatchers("/swagger-ui/**").permitAll().mvcMatchers("/swagger-ui.html").permitAll()
                .mvcMatchers("/documentation").permitAll().mvcMatchers("/").permitAll().anyRequest().authenticated();
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedMethods("*");
            }
        };
    }

    @Bean
    CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.setAllowedOriginPatterns(getAllowedOrigins());
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

}
cgh8pdjw

cgh8pdjw1#

我引用documentation for @WebMvcTest
可用于仅关注Spring MVC组件的Spring MVC测试的注解。
使用此注解将禁用完全自动配置,而仅应用与MVC测试相关的配置(即@Controller、@ControllerAdvice、@JsonComponent、Converter/GenericConverter、Filter、WebMvcConfigurer和HandlerMethodArgumentResolver Bean,但不应用@Component、@Service或@Repository Bean)。
这意味着您的SecurityConfiguration永远不会创建,因为Spring只关注Web层,而不是您的定制组件。
您有两个选项可以修复此问题:

1.导入必要的Bean
@WebMvcTest(PingController.class)
@Import(SecurityConfiguration.class)
class AuthTokenFilterTest {

注意,您还需要导入SecurityConfiguration所依赖的配置/组件,以及这些bean所依赖的所有类,等等。因此,它更有可能是这样的:

@WebMvcTest(PingController.class)
@Import({SecurityConfiguration.class, UserDetailsServiceImpl.class, AuthEntryPointJwt.class})
class AuthTokenFilterTest {

但是这会降低测试的可维护性,如果你添加了一个新的依赖项,比如UserDetailsServiceImpl,你的测试会因为找不到bean而再次中断。

2.使用@SpringBootTest

@SpringBootTest将创建整个应用程序上下文,并为测试做一些细微的修改。

@AutoConfigureMockMvc // <- included in WebMvcTest but not in SpringBootTest
@SpringBootTest
class AuthTokenFilterTest {

这种方法的缺点是测试比较慢,因为要创建整个应用程序上下文,而不仅仅是Web层。我更喜欢这种方法,因为我认为可维护性比测试的执行速度更重要。

相关问题