spring-mvc - 建议 Controller 方法 *before* @Valid 注解被处理

标签 spring-mvc spring-security rate-limiting

我正在使用 Spring MVC 4.1 为 restful web 服务添加速率限制。

我创建了一个 @RateLimited 注释,我可以将其应用于 Controller 方法。 Spring AOP 切面会拦截对这些方法的调用,并在请求过多时抛出异常:

@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RateLimitingAspect {

    @Autowired
    private RateLimitService rateLimitService;

    @Before("execution(* com.example..*.*(.., javax.servlet.ServletRequest+, ..)) " +
            "&& @annotation(com.example.RateLimited)")
    public void wait(JoinPoint jp) throws Throwable {

        ServletRequest request =
            Arrays
                .stream(jp.getArgs())
                .filter(Objects::nonNull)
                .filter(arg -> ServletRequest.class.isAssignableFrom(arg.getClass()))
                .map(ServletRequest.class::cast)
                .findFirst()
                .get();
        String ip = request.getRemoteAddr();
        int secondsToWait = rateLimitService.secondsUntilNextAllowedAttempt(ip);
        if (secondsToWait > 0) {
          throw new TooManyRequestsException(secondsToWait);
        }
    }

这一切都很完美,除非 @RateLimited Controller 方法的参数标记为 @Valid,例如:

@RateLimited
@RequestMapping(method = RequestMethod.POST)
public HttpEntity<?> createAccount(
                           HttpServletRequest request,
                           @Valid @RequestBody CreateAccountRequestDto dto) {

...
}

问题:如果验证失败,验证器抛出MethodArgumentNotValidException,由一个@ExceptionHandler处理,返回错误响应给客户端,永远不会触发我的@Before 从而绕过了速率限制。

如何以优先于参数验证的方式拦截这样的网络请求?

我曾想过使用 Spring 拦截器或普通 servlet 过滤器,但它们是通过简单的 url 模式映射的,我需要通过 GET/POST/PUT/等来区分。

最佳答案

我最终放弃了寻找 AOP 解决方案的尝试,而是创建了一个 Spring Interceptor。拦截器 preHandles 所有请求并监视处理程序为 @RateLimited 的请求。

@Component
public class RateLimitingInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private final RateLimitService rateLimitService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (HandlerMethod.class.isAssignableFrom(handler.getClass())) {
            rateLimit(request, (HandlerMethod)handler);
        }
        return super.preHandle(request, response, handler);
    }

    private void rateLimit(HttpServletRequest request, HandlerMethod handlerMethod) throws TooManyRequestsException {

        if (handlerMethod.getMethodAnnotation(RateLimited.class) != null) {
            String ip = request.getRemoteAddr();
            int secondsToWait = rateLimitService.secondsUntilNextAllowedInvocation(ip);
            if (secondsToWait > 0) {
                throw new TooManyRequestsException(secondsToWait);
            } else {
                rateLimitService.recordInvocation(ip);
            }
        }
    }
}

关于spring-mvc - 建议 Controller 方法 *before* @Valid 注解被处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28975025/

相关文章:

laravel - 如何设置 Laravel 每天的速率限制器?

java - Spring MVC - 检查 session 是否存在

java - Spring @ModelAttribute 模型字段映射

java - @RequestParam 和@PathVariable 封装

java - Spring Boot - 需要 api key 和 x509,但不是所有端点

spring - 自己的 SpringSecurity UserDetailsS​​ervice 不加载用户 - 无法获取当前线程的事务同步 session

javascript - 如何在带有 if-else 语句的 javascript 中使用其 Id 来调用表

mysql - Spring Boot/Hibernate/MySQL 不工作

github - 克服公共(public)项目的 GitHub API 速率限制?

node.js - 速率限制功能在 Nestjs 应用程序中不起作用