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
相关产品推荐
相关产品推荐

