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

Spring Boot+Angular生产环境POST请求403问题求助

问题:Spring Boot POST请求在Apache反向代理Tomcat环境返回403错误

开发Spring Boot + Angular项目时遇到以下问题:

  • POST请求在Postman、本地环境(Spring Boot内置Tomcat)可正常运行
  • 部署到Apache作为反向代理的Tomcat生产环境时返回403错误
  • 服务无需认证,未引入spring-security依赖,尝试禁用CSRF无效,甚至出现要求登录的异常情况

相关代码:

主启动类

import ch.megahertz.swissqrbillsgeneratorapi.properties.FileStorageProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication(scanBasePackages = {"ch.megahertz.swissqrbillsgeneratorapi.*"})
@EnableConfigurationProperties({
        FileStorageProperties.class
})

public class SwissQrBillsGeneratorApiApplication extends SpringBootServletInitializer {

    static Logger logger = LoggerFactory.getLogger(SwissQrBillsGeneratorApiApplication.class);
    public static void main(String[] args) {
        logger.info("Run application");
        SpringApplication.run(SwissQrBillsGeneratorApiApplication.class, args);
    }
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {

        return application.sources(SwissQrBillsGeneratorApiApplication.class);
    }
}

CORS配置类

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedMethods("*")
                .allowedHeaders("*")
                .allowedOrigins("*")
                .allowCredentials(false)
                .maxAge(-1);
    }
}

接口控制器

import ch.megahertz.swissqrbillsgeneratorapi.payload.Invoice;
import ch.megahertz.swissqrbillsgeneratorapi.service.CRMService;
import ch.megahertz.swissqrbillsgeneratorapi.service.FileStorageService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

//@CrossOrigin(origins = {"https://swissqrbillsgenerator.megahertz.ch/","http://localhost:4200/"})
@Slf4j
@RestController
public class ApiController {

    private static final Logger logger = LoggerFactory.getLogger(ApiController.class);

    @Autowired
    private FileStorageService fileStorageService;

    @Autowired
    private CRMService crmService;

    @GetMapping
    public String generateQRBills() {
        log.info("Enter in GeT API");
        return "Get ok";
    }

    @PostMapping("/generate")
    public ResponseEntity<Resource> uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
        System.out.println("Enter in generate API");
        logger.info("Enter in generate API");
        log.info("Enter in generate POST API");
        String fileName = fileStorageService.storeFile(file);

        String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
                .path("/downloadFile/")
                .path(fileName)
                .toUriString();

        Invoice facture = crmService.getFactureInfo(fileName);
        File fileWithQR = fileStorageService.addQrToFile(fileName, facture);

        Resource resource = new UrlResource(fileWithQR.toURI());
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_TYPE,Files.probeContentType(resource.getFile().toPath()))
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName+ "\"")
                .body(resource);
    }
}

解决思路

  • 排查Apache反向代理配置

    • 确认ProxyPass和ProxyPassReverse配置正确,无额外限制请求方法的规则,确保POST请求能完整传递到Tomcat。
    • 检查Apache的mod_security模块是否拦截了请求,可临时禁用该模块测试,或调整规则放行/generate接口。
    • 确保代理传递了必要的请求头部,比如Content-Type、Content-Length,避免因头部丢失导致Tomcat拒绝请求。
  • 检查Tomcat生产环境配置

    • 查看Tomcat全局web.xml,确认是否存在未预期的security-constraint配置,这类配置可能会强制要求认证或限制请求方法。
    • 查看Tomcat的访问日志和错误日志,定位403错误的具体触发原因,日志通常会给出拒绝请求的详细信息。
    • 确认Tomcat的上下文路径与Spring Boot应用的部署路径一致,避免路径映射不匹配导致请求无法被处理。
  • 对比请求差异

    • 对比本地和生产环境的请求头部,确认Angular发送的Origin、Referer等头部是否被Apache正确传递,验证CORS配置在生产环境是否生效。
    • 检查生产环境中文件上传请求的multipart/form-data格式是否正确,Tomcat对请求格式的校验可能比内置Tomcat更严格。
  • 其他排查点

    • 检查Spring Boot服务生成文件的目录权限,确保Tomcat进程拥有读写权限,避免文件操作失败间接引发错误。
    • 绕过Apache代理,直接访问Tomcat端口发送POST请求,确认问题根源在代理还是Tomcat本身。

内容的提问来源于stack exchange,提问作者Marine Gomes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 08:55:19