Spring Boot Mock MVC 将过滤器应用于错误的 url 模式

Spring Boot Mock MVC applying filter to wrong url pattern

我正在向特定 URL 添加管理过滤器,就像这样

    @Bean
    public FilterRegistrationBean<AdminFilter> adminFilterRegistrationBean() {
        FilterRegistrationBean<AdminFilter> registrationBean = new FilterRegistrationBean<>();
        AdminFilter adminFilter = new AdminFilter();
        registrationBean.setFilter(adminFilter);
        registrationBean.addUrlPatterns("/api/user/activate");
        registrationBean.addUrlPatterns("/api/user/deactivate");
        registrationBean.setOrder(Integer.MAX_VALUE);
        return registrationBean;
    }

当我使用 postman 或浏览器对其进行测试时,过滤器已正确应用,仅应用于那些 URL 模式。

但是,当我为它编写测试时,过滤器也以某种方式应用于另一个 URL。

        this.mockMvc.perform(
                get("/api/issue/").header("Authorization", defaultToken)
            ).andDo(print()).andExpect(status().isOk())
            .andExpect(content().json("{}"));

此代码 return 错误代码“403”,在日志中显示因为用户不是管理员,这意味着管理员过滤器应用于“/api/issue/”URL 在模拟 mvc 请求上。

我正在使用 @AutoConfigureMockMvc@Autowired 来实例化 mockMVC。

有人知道为什么会这样吗?


管理员过滤器的完整代码:

@Component
public class AdminFilter extends GenericFilterBean {
    UserService userService;

    @Override
    public void doFilter(
        ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain
    ) throws IOException, ServletException {
        if (userService == null){
            ServletContext servletContext = servletRequest.getServletContext();
            WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
            userService = webApplicationContext.getBean(UserService.class);
        }

        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;

        UUID userId = UUID.fromString((String)httpRequest.getAttribute("userId"));
        User user = userService.fetchUserById(userId);
        if (!user.getIsAdmin()) {
            httpResponse.sendError(HttpStatus.FORBIDDEN.value(), "User is not an admin");
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

测试文件的完整代码:

@SpringBootTest()
@AutoConfigureMockMvc
@Transactional
public class RepositoryIntegrationTests {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private RepositoryRepository repositoryRepository;

    @Autowired
    private UserRepository userRepository;

    private String defaultToken;
    private String otherToken;

    @BeforeEach
    void init() {
        User defaultUser = userRepository.save(new User("username", "email@mail.com", "password"));
        System.out.println(defaultUser);
        User otherUser = userRepository.save(new User("other", "other@mail.com", "password"));
        defaultToken = "Bearer " + generateJWTToken(defaultUser);
        otherToken = "Bearer " + generateJWTToken(otherUser);
    }

    private String generateJWTToken(User user) {
        long timestamp = System.currentTimeMillis();
        return Jwts.builder().signWith(SignatureAlgorithm.HS256, Constants.API_SECRET_KEY)
            .setIssuedAt(new Date(timestamp))
            .setExpiration(new Date(timestamp + Constants.TOKEN_VALIDITY))
            .claim("userId", user.getId())
            .compact();
    }

    @Test
    public void shouldReturnAllRepositoriesAvailableToUser() throws Exception {
        this.mockMvc.perform(
                get("/api/issue/").header("Authorization", defaultToken)
            ).andExpect(status().isOk())
            .andExpect(content().json("{}"));
    }
}

您的 AdminFilter 被注册了两次。一次通过 FilterRegistrationBean,一次由于它是 @Component,因此被组件扫描检测到。

要修复,请执行以下两件事之一

  1. 删除@Component
  2. FilterRegistrationBean重新使用自动创建的实例。

删除 @Component 很简单,只需将其从 class.

中删除即可

对于选项 2,您可以将自动配置的过滤器注入 FilterRegistrationBean 配置方法,而不是自己创建。

@Bean
public FilterRegistrationBean<AdminFilter> adminFilterRegistrationBean(AdminFilter adminFilter) {
    FilterRegistrationBean<AdminFilter> registrationBean = new FilterRegistrationBean<>(adminFilter);
    registrationBean.addUrlPatterns("/api/user/activate");
    registrationBean.addUrlPatterns("/api/user/deactivate");
    registrationBean.setOrder(Integer.MAX_VALUE);
    return registrationBean;
}

这样做的另一个好处是您可以使用自动装配来设置依赖项,而不是在 init 方法中进行查找。我还建议使用 OncePerRequestFilter。这将大大清理您的过滤器。

@Component
public class AdminFilter extends OncePerRequestFilter {
    
    private final UserService userService;

    public AdminFilter(UserService userService) {
        this.userService=userService;
    }

    @Override
    protected void doFilter(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain filterChain) throws IOException, ServletException {

        UUID userId = UUID.fromString((String)httpRequest.getAttribute("userId"));
        User user = userService.fetchUserById(userId);
        if (!user.getIsAdmin()) {
            httpResponse.sendError(HttpStatus.FORBIDDEN.value(), "User is not an admin");
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
}