Skip to content

当表单大小超大时(超过Tomcat设置的大小)没有触发该有的异常 #14423

Description

@youngledo

Describe the bug
正常情况下,当表单参数超过设置的大小会在此类这块代码进行判断:

Image

并且会抛出这样的错:

高版本为413 - Request Entity Too Large

Image

提前解析了参数,导致此变量被设置为true,后续即使表单超大也都被跳过去了。
Image

最后却提示一个与表单超大无关的异常:

Image

那这个异常是什么情况呢?是因为表单太大了,被截断了,而刚好token也被截断了,之后报认证失败。


我调试了代码,发现nacos在启动时会为了性能缓存了URL和默认参数,下图为默认参数解析

Image Image

正因为这些默认参数,每次请求进来时都会提前解析了,导致Tomcat的Request类的变量被设置为true,后续表单参数再大也无法拦截。

Expected behavior
抛出由底层的Tomcat请求体太大的异常信息,即上述截图。

Actually behavior
A clear and concise description of what you actually to happen.

How to Reproduce
Steps to reproduce the behavior:

  1. 准备一个超过3MB(因为默认2MB,当然你可以调小,根据实际情况)的文件。
  2. 然后使用curl请求接口:
curl http://localhost:8848/nacos/v1/cs/configs \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Expect:' \
  --data-binary @mysoft-emc-long.txt

Desktop (please complete the following information):

  • OS: [e.g. Centos]
  • Version 2.5.0
  • Module config

Additional context
代码我已经粗略读了一遍,但为了更清晰分析问题,以下为AI输出:

                                                                                                                                                                                                           
  ControllerMethodsCache 的作用                                                                                                                                                                            
                                                                                                                                                                                                           
  1. 核心功能:方法缓存和路由匹配                                                                                                                                                                          
                                                                                                                                                                                                           
  从代码可以看出,这个类主要做两件事:                                                                                                                                                                     
                                                                                                                                                                                                           
  // 缓存所有的 Controller 方法                                                                                                                                                                            
  private ConcurrentMap<RequestMappingInfo, Method> methods = new ConcurrentHashMap<>();                                                                                                                   
                                                                                                                                                                                                           
  // URL 到方法的映射                                                                                                                                                                                      
  private final ConcurrentMap<String, List<RequestMappingInfo>> urlLookup = new ConcurrentHashMap<>();                                                                                                     
                                                                                                                                                                                                           
  作用:                                                                                                                                                                                                   
  - 缓存所有 Controller 的 @RequestMapping、@GetMapping、@PostMapping 等注解的方法                                                                                                                         
  - 快速查找:根据 URL 和 HTTP 方法找到对应的处理方法                                                                                                                                                      
  - 路由匹配:支持复杂的路由条件匹配(路径、参数等)                                                                                                                                                       
                                                                                                                                                                                                           
  2. 为什么需要这个类?                                                                                                                                                                                    
                                                                                                                                                                                                           
  让我查看一下在 AuthFilter 中是如何使用的:                                                                                                                                                               
                                                                                                                                                                                                           
⏺ Read(core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java)                                                                                                                                     
  ⎿  Read 90 lines                                                                                                                                                                                         
                                                                                                                                                                                                           
⏺ 完美!现在我可以清楚地解释了。                                                                                                                                                                           
                                                                                                                                                                                                           
  为什么需要 ControllerMethodsCache                                                                                                                                                                        
                                                                                                                                                                                                           
  核心原因:在 Filter 层面进行权限控制                                                                                                                                                                     
                                                                                                                                                                                                           
  看 AuthFilter.java:77-90 的逻辑:                                                                                                                                                                        
                                                                                                                                                                                                           
  // 第77行:获取当前请求对应的 Controller 方法                                                                                                                                                            
  Method method = methodsCache.getMethod(req);                                                                                                                                                             
                                                                                                                                                                                                           
  if (method == null) {                                                                                                                                                                                    
      chain.doFilter(request, response);  // 不是 Controller 方法,直接放行                                                                                                                                
      return;                                                                                                                                                                                              
  }                                                                                                                                                                                                        
                                                                                                                                                                                                           
  // 第84行:检查方法是否有 @Secured 注解                                                                                                                                                                  
  if (method.isAnnotationPresent(Secured.class)) {                                                                                                                                                         
      // 需要权限验证                                                                                                                                                                                      
      Secured secured = method.getAnnotation(Secured.class);                                                                                                                                               
      // ... 进行权限验证                                                                                                                                                                                  
  }                                                                                                                                                                                                        
                                                                                                                                                                                                           
  传统 Spring MVC 的问题                                                                                                                                                                                   
                                                                                                                                                                                                           
  没有 ControllerMethodsCache 的做法:                                                                                                                                                                     
                                                                                                                                                                                                           
  请求 → Filter → DispatcherServlet → HandlerMapping → Controller                                                                                                                                          
                      ↑                                                                                                                                                                                    
                   在这里不知道请求会映射到哪个 Controller 方法                                                                                                                                            
                  所以无法在 Filter 层判断是否需要权限验证                                                                                                                                                 
                                                                                                                                                                                                           
  问题:                                                                                                                                                                                                   
  - Filter 在 DispatcherServlet 之前执行                                                                                                                                                                   
  - Filter 不知道请求最终会映射到哪个 Controller 方法                                                                                                                                                      
  - 无法在 Filter 层进行方法级别的权限控制                                                                                                                                                                 
                                                                                                                                                                                                           
  Nacos 的解决方案                                                                                                                                                                                         
                                                                                                                                                                                                           
  使用 ControllerMethodsCache:                                                                                                                                                                            
                                                                                                                                                                                                           
  请求 → Filter (AuthFilter)                                                                                                                                                                               
           ├─ ControllerMethodsCache.getMethod(req) → 获取目标方法                                                                                                                                         
           ├─ 检查 @Secured 注解                                                                                                                                                                           
           ├─ 进行权限验证                                                                                                                                                                                 
           └─ 验证通过 → DispatcherServlet → Controller                                                                                                                                                    
                                                                                                                                                                                                           
  优势:                                                                                                                                                                                                   
  1. ✅ 在 Filter 层就能获取目标方法                                                                                                                                                                       
  2. ✅ 支持参数条件匹配:@RequestMapping(params="accessToken=123")                                                                                                                                        
  3. ✅ 性能优化:缓存了所有方法映射,不需要每次都反射扫描                                                                                                                                                 
  4. ✅ 统一权限控制:所有带 @Secured 注解的方法都会被拦截                                                                                                                                                 
                                                                                                                                                                                                           
  详细功能拆解                                                                                                                                                                                             
                                                                                                                                                                                                           
  1. 方法缓存                                                                                                                                                                                              
                                                                                                                                                                                                           
  // 启动时扫描并缓存所有 Controller 方法                                                                                                                                                                  
  public void initClassMethod(String packageName) {                                                                                                                                                        
      // 扫描 @RequestMapping, @GetMapping 等注解                                                                                                                                                          
      // 缓存到 methods 和 urlLookup 中                                                                                                                                                                    
  }                                                                                                                                                                                                        
                                                                                                                                                                                                           
  2. 路由匹配                                                                                                                                                                                              
                                                                                                                                                                                                           
  public Method getMethod(HttpServletRequest request) {                                                                                                                                                    
      // 1. 根据 URL 和 HTTP 方法查找候选方法                                                                                                                                                              
      List<RequestMappingInfo> requestMappingInfos = urlLookup.get(urlKey);                                                                                                                                
                                                                                                                                                                                                           
      // 2. 进行精细匹配(包括参数条件)                                                                                                                                                                   
      List<RequestMappingInfo> matchedInfo = findMatchedInfo(...);                                                                                                                                         
                                                                                                                                                                                                           
      // 3. 返回最佳匹配的方法                                                                                                                                                                             
      return methods.get(bestMatch);                                                                                                                                                                       
  }                                                                                                                                                                                                        
                                                                                                                                                                                                           
  3. 参数条件匹配(关键!)                                                                                                                                                                                
                                                                                                                                                                                                           
  这就是导致参数提前解析的原因:                                                                                                                                                                           
                                                                                                                                                                                                           
  private List<RequestMappingInfo> findMatchedInfo(...) {                                                                                                                                                  
      for (RequestMappingInfo requestMappingInfo : requestMappingInfos) {                                                                                                                                  
          // 检查参数条件:@RequestMapping(params="key=value")                                                                                                                                             
          ParamRequestCondition matchingCondition = requestMappingInfo                                                                                                                                     
              .getParamRequestCondition()                                                                                                                                                                  
              .getMatchingCondition(request);  // ⚠️ 这里会调用 getParameter()                                                                                                                             
                                                                                                                                                                                                           
          if (matchingCondition != null) {                                                                                                                                                                 
              matchedInfo.add(requestMappingInfo);                                                                                                                                                         
          }                                                                                                                                                                                                
      }                                                                                                                                                                                                    
      return matchedInfo;                                                                                                                                                                                  
  }                                                                                                                                                                                                        
                                                                                                                                                                                                           
  支持的参数条件示例:                                                                                                                                                                                     
                                                                                                                                                                                                           
  @RestController                                                                                                                                                                                          
  public class ConfigController {                                                                                                                                                                          
                                                                                                                                                                                                           
      // 只有当请求中包含 accessToken 参数且值为 admin 时才匹配                                                                                                                                            
      @PostMapping(value = "/config", params = "accessToken=admin")                                                                                                                                        
      public Result postConfig() { ... }                                                                                                                                                                   
                                                                                                                                                                                                           
      // 只有当请求中不包含 debug 参数时才匹配                                                                                                                                                             
      @GetMapping("/config", params="!debug")                                                                                                                                                              
      public Result getConfig() { ... }                                                                                                                                                                    
  }                                                                                                                                                                                                        
                                                                                                                                                                                                           
  总结                                                                                                                                                                                                     
                                                                                                                                                                                                           
  为什么需要 ControllerMethodsCache?                                                                                                                                                                      
  ┌───────────────────┬───────────────────────────────────────────────┐                                                                                                                                    
  │       原因        │                     说明                      │                                                                                                                                    
  ├───────────────────┼───────────────────────────────────────────────┤                                                                                                                                    
  │ Filter 层权限控制 │ 在请求到达 Controller 之前就进行权限验证      │                                                                                                                                    
  ├───────────────────┼───────────────────────────────────────────────┤                                                                                                                                    
  │ 方法级别注解支持  │ 识别 @Secured 注解,判断是否需要权限验证      │                                                                                                                                    
  ├───────────────────┼───────────────────────────────────────────────┤                                                                                                                                    
  │ 参数条件匹配      │ 支持 @RequestMapping(params="...") 的路由匹配 │                                                                                                                                    
  ├───────────────────┼───────────────────────────────────────────────┤                                                                                                                                    
  │ 性能优化          │ 缓存方法映射,避免每次请求都反射扫描          │                                                                                                                                    
  └───────────────────┴───────────────────────────────────────────────┘                                                                                                                                    
  权衡                                                                                                                                                                                                     
                                                                                                                                                                                                           
  优点:                                                                                                                                                                                                   
  - ✅ 实现了统一的权限控制                                                                                                                                                                                
  - ✅ 支持复杂的路由匹配条件                                                                                                                                                                              
                                                                                                                                                                                                           
  缺点:                                                                                                                                                                                                   
  - ❌ 提前触发了参数解析(这就是您遇到的问题根源)                                                                                                                                                        
  - ❌ 与 Tomcat 的 maxHttpFormPostSize 校验时机产生冲突 

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions