弹簧过滤器使用cloud-zuul-rate-limiter返回500而不是429

问题描述

我尝试使用spring-cloud-zuul-ratelimit限制到我的网关的传入请求,并在遵循文档后返回429个太多请求 docs是here

注意: 我使用spring 2.2.7和JDK8

问题仍然存在,并且经过长时间的投入,我认为它们是Filters中的一些问题,因为它转到LogFilterService.java中的filterchain.dofilter并在所有情况下均以正确的响应返回,然后消失并显示500内部服务器错误而没有任何清除仅例外 com.netflix.zuul.exception.ZuulException:429 因此,我将使用所有自定义过滤器来尝试发现错误

这是日志中的例外情况

2020-11-12T15:40:22.794Z (UTC+0),[http-nio-8085-exec-8] WARN  o.s.c.n.z.f.post.SendErrorFilter - Error during filtering
com.netflix.zuul.exception.ZuulException: 429 TOO_MANY_REQUESTS
    at com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.RateLimitExceededException.<init>(RateLimitExceededException.java:13)
    at com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.filters.RateLimitPreFilter.lambda$run$0(RateLimitPreFilter.java:125)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.filters.RateLimitPreFilter.run(RateLimitPreFilter.java:87)
    at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:117)
    at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193)
    at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157)
    at com.netflix.zuul.FilterProcessor.preRoute(FilterProcessor.java:133)
    at com.netflix.zuul.ZuulRunner.preRoute(ZuulRunner.java:105)
    at com.netflix.zuul.http.ZuulServlet.preRoute(ZuulServlet.java:125)
    at com.netflix.zuul.http.ZuulServlet.service(ZuulServlet.java:74)
    at org.springframework.web.servlet.mvc.ServletWrappingController.handleRequestInternal(ServletWrappingController.java:166)
    at org.springframework.cloud.netflix.zuul.web.ZuulController.handleRequest(ZuulController.java:45)
    at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:52)
    at org.springframework.web.servlet.dispatcherServlet.dodispatch(dispatcherServlet.java:1040)
    at org.springframework.web.servlet.dispatcherServlet.doService(dispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at brave.servlet.TracingFilter.doFilter(TracingFilter.java:68)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.orange.combo.gatewayservice.config.httpLoggingFilter.RequestLogFilter.doFilter(RequestLogFilter.java:98)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:176)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.logout.logoutFilter.doFilter(logoutFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at brave.servlet.TracingFilter.doFilter(TracingFilter.java:87)
    at org.springframework.cloud.sleuth.instrument.web.LazyTracingFilter.doFilter(TraceWebServletAutoConfiguration.java:139)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcmetricsFilter.doFilterInternal(WebMvcmetricsFilter.java:109)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.orange.combo.gatewayservice.config.apiFactoryFilter.HttpRequestCustomFilter.doFilter(HttpRequestCustomFilter.java:28)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
    at org.apache.tomcat.util.net.socketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

@Component
public class RequestLogFilter implements Filter {

  private static final Logger logger = LoggerFactory.getLogger("kibana-logger");
  private final String regex;

  public RequestLogFilter() {
    this.regex = Stream
            .of(".*/v2/api-docs",".*/swagger-resources",".*/swagger-ui.html",".*/webjars",".*/bot-management/getCampaign")
            .reduce((str1,str2) -> String.format("%s|%s",str1,str2))
            .map(str -> String.format("(%s).*",str))
            .orElse("");
  }

  @Override
  public void init(FilterConfig filterConfig) throws servletexception {

  }

  @Override
  public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,servletexception {
    final String path = ((HttpServletRequest) request).getServletPath();

    if (!path.matches(this.regex)) {
      request = new CustomHttpServletRequestWrapper((HttpServletRequest) request);
      response = new CustomHttpServletResponseWrapper((HttpServletResponse) response);

      if (((HttpServletRequest) request).getHeader("authorization") != null) {
        final String rolePrefix = "ROLE_";
        final String defaultRoleName = "ROLE_GUEST";
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        final String email = authentication.getName();
        final String roleName = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority).filter(authority -> authority.startsWith(rolePrefix)).findFirst().orElse(defaultRoleName)
                .replaceFirst(rolePrefix,"");
        MDC.put("email",email);
        MDC.put("role",roleName);
      } else {
        MDC.remove("email");
        MDC.remove("role");
      }

      if (((HttpServletRequest) request).getHeader("host") != null) {
        final String host = ((HttpServletRequest) request).getHeader("host");
        MDC.put("host",host);
      }

      /* request info */
      final String method = ((HttpServletRequest) request).getmethod();
      final String url = ((HttpServletRequest) request).getRequestURL().toString();
      logger.info(String.format("REST,request,%s,%s",method,url));

      /* request headers */
      final List<String> requestHeaders = new LinkedList<>();
      final Enumeration<String> headerNames = ((HttpServletRequest) request).getHeaderNames();
      while (headerNames.hasMoreElements()) {
        final String headerName = headerNames.nextElement();
        if (!headerName.equals("authorization")) {
          final String headerValue = ((HttpServletRequest) request).getHeader(headerName);
          requestHeaders.add(String.format("%s: %s",headerName,headerValue));
        }
      }
      logger.info(String.format("REST,headers,requestHeaders.toString()));

      /* request data */
      final String requestData = ((CustomHttpServletRequestWrapper) request).getBody().replaceAll("\n","");
      final StringBuilder stringBuilder = new StringBuilder();
      if (request.getParameterMap().size() > 0) {
        stringBuilder.append("{");
        request.getParameterMap().forEach((key,value) -> stringBuilder.append(key).append(": ").append(value[0]).append(","));
        stringBuilder.delete(stringBuilder.length() - 2,stringBuilder.length());
        stringBuilder.append("}");
      }
      logger.info(String.format("REST,data,requestData,stringBuilder.toString()));

      /* response */
      try {
         // --problem is hear
        chain.doFilter(request,response);
        response.flushBuffer();
      }finally {
        final String removingWhitespacesRegex = "\n";

        final Integer status = ((CustomHttpServletResponseWrapper) response).getStatus();
        final String contentType = response.getContentType();
        String responseData = ((CustomHttpServletResponseWrapper) response).getBody().replaceAll(removingWhitespacesRegex,"");
        if (status < 400)
          logger.info(String.format("REST,response,%d,status,contentType,responseData));
        else
          logger.error(String.format("REST,responseData));
      }
    } else {
      chain.doFilter(request,response);
    }
  }

  @Override
  public void destroy() {

  }
}
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HttpRequestCustomFilter implements Filter {

  @Override
  public void init(FilterConfig filterConfig) throws servletexception {

  }

  @Override
  public void doFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain) throws IOException,servletexception {
    CustomHeadersHttpServletRequestWrapper request = new CustomHeadersHttpServletRequestWrapper((HttpServletRequest) servletRequest);
    String comboToken = request.getHeader("Combo-Token");
    if(Objects.nonNull(comboToken)) {
      request.addHeader("Authorization",request.getHeader("Combo-Token"));
    }
    filterChain.doFilter(request,servletResponse);
  }

  @Override
  public void destroy() {

  }
}
@Component
public class UserFilter extends ZuulFilter {

  private final UserRepository userRepository;
  private final HttpErrorHandlingService errorHandlingService;

  @Value("${token.validitySeconds}")
  private long validitySeconds;

  private static final Logger LOGGER = LoggerFactory.getLogger("kibana-logger");

  public UserFilter(UserRepository deletedUserRepository,HttpErrorHandlingService errorHandlingService) {
    this.userRepository = deletedUserRepository;
    this.errorHandlingService = errorHandlingService;
  }


  @Override
  public String filterType() {
    return FilterConstants.PRE_TYPE; //Executed before the request is routed
  }

  @Override
  public int filterOrder() {
    return 0;
  }

  @Override
  public boolean shouldFilter() {
    return true; //Indicates that run() method should be invoked
  }

  @Override
  @SuppressWarnings("unchecked")
  public Object run() {
    //A wrapper around the request,shared by all filters and is unique to each request
    RequestContext requestContext = RequestContext.getCurrentContext();
    final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    final String email = authentication.getName();
    final User user = userRepository.findByEmail(email);
    try {
      //Set Response Body here
      if(Objects.nonNull(user)) {
        if(user.isDeleted())
          buildresponse(requestContext,HttpStatus.FORBIDDEN,OrangeErrorInfo.FORBIDDEN_USER,"User is deleted");
        else {
          //The JWT details are only available for secured routes (i.e. routes requiring access token)
          if(authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
            final Map<String,Object> jwtClaims = (Map<String,Object>)((OAuth2AuthenticationDetails)authentication.getDetails())
                    .getDecodedDetails();
            if(isInvalidToken(user.getTokenLastUpdated(),(Long)jwtClaims.get("exp")))
              buildresponse(requestContext,HttpStatus.UNAUTHORIZED,OrangeErrorInfo.EXPIRED_CREDENTIALS,"COMBO token expired");
          }
        }
      }
    } catch (IOException e) {
      LOGGER.error("Error while serializing object to json");
    }
    return null;
  }

  /**
   * builds a response depending on the request context and client error
   * @param requestContext the current request context
   * @param httpStatus the response HTTP status code
   * @param orangeErrorInfo the custom error code and message (ODI standardized)
   * @param errorMessage the custom error description message
   * @throws IOException if converting the response body object to JSON has Failed (propagated)
   */
  private void buildresponse(RequestContext requestContext,HttpStatus httpStatus,OrangeErrorInfo orangeErrorInfo,String errorMessage) throws IOException {
    requestContext.setSendZuulResponse(false);
    requestContext.setResponseStatusCode(httpStatus.value());
    requestContext.getResponse().setContentType(MediaType.APPLICATION_JSON_VALUE);

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
    requestContext.setResponseBody(errorHandlingService.buildErrorResponseBody(
            requestContext.getRequest(),orangeErrorInfo,errorMessage));
  }

  /**
   * computes the creation date of the token using the validity duration defined in different environments
   * (creation date = expiration date - validity duration) and checks whether this date is earlier that the token's
   * last updated date
   *
   * @param tokenLastUpdated the date where the token was last modified/updated
   * @param expiration the token expiration date in seconds
   * @return true if the creation date is earlier than last updated date
   */
  private boolean isInvalidToken(Date tokenLastUpdated,Long expiration) {
    Instant expirationDateTime = Instant.ofEpochSecond(expiration);
    Instant tokenLastUpdatedDateTime = tokenLastUpdated.toInstant();
    Duration duration = Duration.ofSeconds(validitySeconds);
    return expirationDateTime.minus(duration).isBefore(tokenLastUpdatedDateTime.truncatedTo(ChronoUnit.SECONDS));
  }
}
@Controller
public class CustomErrorController extends BasicErrorController {

    @Autowired
    private HttpErrorHandlingService httpErrorHandlingService;

    public CustomErrorController(ErrorAttributes errorAttributes,ServerProperties errorProperties,List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes,errorProperties.getError(),errorViewResolvers);
    }

    @Override
    public ResponseEntity<Map<String,Object>> error(HttpServletRequest request) {
        Exception ex = ((Exception)request.getAttribute("javax.servlet.error.exception"));
        if(Objects.nonNull(ex)) {
            if(ex.getCause() instanceof MultipartException)
                throw new ComboHttpRequestException("The submitted value for content-type header isn't supported",OrangeErrorInfo.INVALID_HEADER_VALUE);
            else if(ex instanceof RequestRejectedException)
                throw new ComboHttpRequestException(ex.getMessage(),request);
            else if(ex instanceof ZuulException)
                if(new RateLimitExceededException().getMessage().contains(ex.getMessage()))
                    throw new ZuulRateLimitException("The application has made too many calls and has exceeded the rate limit for this service",OrangeErrorInfo.TOO_MANY_REQUESTS);
        }
        return super.error(request);
    }

    @ExceptionHandler(ComboHttpRequestException.class)
    public ResponseEntity<String> handleError(ComboHttpRequestException ex) {
        try {
            return ResponseEntity.badRequest()
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(httpErrorHandlingService.buildErrorResponseBody(String.valueOf(ex.getRequest().getAttribute("javax.servlet.error.request_uri")),ex.getRequest().getmethod(),ex.getorangeErrorInfo(),ex.getMessage()));
        } catch (IOException e) {
            e.printstacktrace();
        }
        return ResponseEntity.badRequest().build();
    }

    @ExceptionHandler(ZuulRateLimitException.class)
    public ResponseEntity<String> handleZuulError(ZuulRateLimitException ex){
        try {

            return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(httpErrorHandlingService.buildErrorResponseBody(String.valueOf(ex.getRequest().getAttribute("javax.servlet.error.request_uri")),ex.getMessage()));
        } catch (IOException e) {
            e.printstacktrace();
        }
        return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build();

    }

进入这些过滤器并返回com.netflix.zuul.exception.ZuulException:成功地返回429 TOO_MANY_REQUESTS,但在邮递员中返回500

添加spring-cloud-zuul-ratelimit之前,它们都工作正常,我按照doc的顺序进行操作 请帮助

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)