Angular-SpringBoot3文件上传下载时的CORS问题求助
核心问题分析
你的CORS问题主要来自三个方面:Spring Security的CORS配置冲突、前后端请求方式不匹配、文件请求的参数/格式错误,这些问题叠加导致预请求(OPTIONS)无法正确返回CORS头。
具体修复步骤
1. 修复Spring Security的CORS配置
你在SecurityFilterChain中禁用了CORS(.cors().disable()),这会直接覆盖控制器上的@CrossOrigin注解,导致CORS头无法正常添加。正确做法是启用CORS并配置全局规则:
修改SecurityConfiguration:
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfiguration { private final JwtAuthenticationFilter jwtAuthFilter; private final AuthenticationProvider authenticationProvider; private final LogoutHandler logoutHandler; // 配置全局CORS规则 @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200")); // 允许你的前端源 configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowCredentials(true); // 如需携带认证信息(如JWT)则开启 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf() .disable() .cors(cors -> cors.configurationSource(corsConfigurationSource())) // 启用CORS并绑定全局配置 .authorizeHttpRequests() .requestMatchers("/api/v1/**", "/swagger-ui/**", "/v3/api-docs/**") .permitAll() .anyRequest() .authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) .logout() .logoutUrl("/api/v1/auth/logout") .addLogoutHandler(logoutHandler) .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext()) ; return http.build(); } }
配置完成后可移除控制器上的@CrossOrigin注解,全局配置已覆盖相关规则。
2. 修复前后端下载接口的请求方式不匹配
后端下载接口定义为GET请求,但前端使用POST发起请求,且GET请求不支持@RequestBody参数,这会直接导致请求报错,进而影响CORS头的返回。
后端修改:
将下载接口改为POST,同时修正参数逻辑:
@RequestMapping(path = "/download-excel", method = RequestMethod.POST) public ResponseEntity<byte[]> downloadExcelFile(@RequestBody List<Long> ids) throws IOException { ExcelDownloader helper = new ExcelDownloader(); List<Navire> toDownload = new ArrayList<>(); for (Long id : ids) { toDownload.add(repository.getReferenceById(id)); } HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentDispositionFormData("attachment", "entity.xlsx"); byte[] bytes = helper.downloadToExcel(toDownload); return new ResponseEntity<>(bytes, headers, HttpStatus.OK); }
(注:已删除原代码中重复定义的toDownload变量)
前端修改:
直接传递ID列表作为请求体,无需额外设置不必要的请求头:
downloadExcelFile(toDownload: any[]): Observable<HttpResponse<Blob>> { const idList: number[] = toDownload.map(value => value.id); return this.http.post<Blob>(`${this.API_URL}download-excel`, idList, { observe: 'response', responseType: 'blob' as 'json' }).pipe( catchError((error: any) => { console.error('Error occurred during file download:', error); throw error; }) ); }
3. 修复前端文件上传的格式问题
直接传递File对象会导致请求的Content-Type不是multipart/form-data,后端无法解析@RequestParam("file"),同时可能触发预请求异常。需用FormData包装文件:
uploadExcelFile(file: File): Observable<any> { const formData = new FormData(); formData.append('file', file); // key需与后端@RequestParam的参数名一致 return this.http.post(`${this.API_URL}upload-excel`, formData); }
Angular会自动为FormData设置正确的Content-Type,后端即可正常解析文件。
4. 验证预请求有效性
修复完成后,打开浏览器开发者工具的Network面板,找到文件请求的OPTIONS预请求,检查响应头是否包含以下字段:
Access-Control-Allow-Origin: http://localhost:4200Access-Control-Allow-Methods: POST, GET, OPTIONS...Access-Control-Allow-Headers: *
若这些字段存在,说明CORS配置已生效。
内容的提问来源于stack exchange,提问作者azerELweed

