在处理* @Valid注释之前建议控制器方法*

时间:2015-03-10 22:09:19

标签: spring-mvc spring-security rate-limiting

我正在使用Spring MVC 4.1为一个安静的Web服务添加速率限制。

我创建了一个@RateLimited注释,可以应用于控制器方法。 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控制器方法的参数标记为@Valid,例如:

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

...
}

问题:如果验证失败,验证程序将抛出由MethodArgumentNotValidException处理的@ExceptionHandler,它会向客户端返回错误响应,从不触发我的@Before因此绕过速率限制。

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

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

2 个答案:

答案 0 :(得分:3)

我最终放弃了尝试寻找AOP解决方案并改为创建一个Spring Interceptor。拦截器preHandle所有请求和监视处理程序为@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);
            }
        }
    }
}

答案 1 :(得分:0)

看看你是否可以为@@ AfterThrowing建议实现类似的逻辑,它也会有类似的切入点。