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
,因此被组件扫描检测到。
要修复,请执行以下两件事之一
- 删除
@Component
- 为
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);
}
}
我正在向特定 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
,因此被组件扫描检测到。
要修复,请执行以下两件事之一
- 删除
@Component
- 为
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);
}
}