You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中同时传递MultipartFile与JSON及403问题排查

Spring Boot 同时传递MultipartFile与JSON的实现及403权限问题解决

一、403 Forbidden问题排查与解决

1. 权限前缀不匹配

Spring Security的hasRole()方法默认会自动给权限添加ROLE_前缀,如果你数据库或认证逻辑中用户的权限是ADD_PRODUCT(不带前缀),那么hasRole('ADD_PRODUCT')实际校验的是ROLE_ADD_PRODUCT,自然会校验失败。

解决方法二选一:

  • 把注解改成@PreAuthorize("hasAuthority('ADD_PRODUCT')"),hasAuthority()不会添加前缀,直接匹配权限字符串;
  • 在Spring Security配置类中关闭自动添加前缀:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/product/add").hasRole("ADD_PRODUCT")
                // 其他配置...
            )
            .formLogin(Customizer.withDefaults());
        
        // 关闭ROLE_前缀
        http.getSharedObject(AuthorizationManagerRequestMatcherRegistry.class)
            .setRolePrefix("");
        
        return http.build();
    }
}

2. CSRF保护拦截

Spring Security默认开启CSRF保护,当使用multipart/form-data格式提交请求时,需要携带CSRF Token才能通过校验。

解决方法:

  • 测试环境可以临时关闭CSRF(生产环境不建议):
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // 关闭CSRF
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/product/add").hasAuthority("ADD_PRODUCT")
                // 其他配置...
            );
        return http.build();
    }
}
  • 生产环境需传递CSRF Token:在请求头中添加X-XSRF-TOKEN,值从Cookie中的XSRF-TOKEN获取。

3. 请求Content-Type配置错误

你的接口consumes同时声明了MULTIPART_FORM_DATA_VALUE和APPLICATION_JSON_VALUE,但实际请求是multipart/form-data格式,这可能导致Spring Security的过滤器链处理请求时出现异常,间接引发403。

修改接口的consumes,只保留multipart/form-data:

@PostMapping(value = "/add", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)

二、同时传递MultipartFile与JSON的正确实现

1. 控制器代码调整

确保接口参数使用@RequestPart,并且consumes只指定multipart/form-data:

@RestController
@RequestMapping("/api/product")
public class ProductController {

    @Autowired
    ProductService productService;
    
    @PreAuthorize(value = "hasAuthority('ADD_PRODUCT')")
    @PostMapping(value = "/add", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public HttpEntity<?> addProduct(@CurrentUser User user,
                                    @RequestPart("files") MultipartFile multipartFile,
                                    @Valid @RequestPart("info") ProductDto productDto) {
        ApiResponse apiResponse = productService.addProduct(user, productDto, multipartFile);
        return ResponseEntity.status(apiResponse.isSuccess() ? 201 : 409).body(apiResponse);
    }
}

2. 请求发送规范

以接口测试工具为例:

  • 请求方法选POST,URL填对应接口地址;
  • 请求体选form-data;
  • 添加两个参数:
    • 第一个参数key为files,类型选File,上传本地文件;
    • 第二个参数key为info,类型选Text,输入JSON格式的ProductDto数据,并且在该参数的请求头中添加Content-Type: application/json(确保Spring能正确解析为ProductDto对象)。

3. 校验异常处理

如果ProductDto的校验失败(比如name为空),会抛出MethodArgumentNotValidException,建议全局捕获该异常并返回友好提示:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResponse> handleValidationExceptions(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.toList());
        return ResponseEntity.badRequest().body(new ApiResponse(false, String.join(", ", errors)));
    }
}

内容的提问来源于stack exchange,提问作者Dadaboy Masharipov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 21:41:11