SpringBoot/Tomcat/Apache反向代理POST请求返回403排查与修复
问题概述
- 部署环境:未集成Spring Security的SpringBoot应用,运行于Tomcat容器,前端使用Apache做反向代理
- 故障现象:发起POST请求时返回403 Forbidden状态码
- 核心诉求:
- 明确HTTP请求/响应中哪些特征会触发403返回码
- 定位403的具体返回组件(SpringBoot/Tomcat/Apache反向代理)
- 给出对应修复方案

现有配置参考
pom.xml 依赖
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-math3</artifactId> <version>3.6.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.4</version> </dependency> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> <version>${open-api}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>org.jfree</groupId> <artifactId>jfreechart</artifactId> <version>1.0.19</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
Apache httpd.conf 配置
SSLCipherSuite 'kEECDH+ECDSA kEECDH kEDH HIGH +SHA !aNULL !eNULL !LOW !MEDIUM !MD5 !EXP !DSS !PSK !SRP !kECDH SSLHonorCipherOrder On SSLRandomSeed startup file:/dev/urandom 2048 SSLRandomSeed connect builtin DocumentRoot /apache/htdocs AddDefaultCharset utf-8 <VirtualHost *:80> RewriteEngine On RewriteRule ^/(.*)$ https://%{HTTP_HOST}/$1 [redirect,last] <Directory /apache/htdocs> Require all granted Options None AllowOverride None </Directory> </VirtualHost> <VirtualHost *:443> SSLEngine On <Directory /apache/htdocs> Require all granted Options None AllowOverride None </Directory> SSLEngine on SSLCertificateFile "/etc/letsencrypt/live/www.guli.com/fullchain.pem" SSLCertificateKeyFile "/etc/letsencrypt/live/www.guli.com/privkey.pem" </VirtualHost> <IfModule mod_proxy.c> <Location "/"> ProxyPass "http://localhost:50080/main/" connectiontimeout=5 timeout=600 ProxyPassReverse "http://localhost:50080/main/" ProxyPassReverseCookiePath "/main/" "/" ProxyPreserveHost On Header always set Access-Control-Allow-Origin "*" Header always set Access-Control-Allow-Headers "x-requested-with, Content-Type, origin, authorization, accept, client-security-token" Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT" Header always set Access-Control-Expose-Headers "Content-Security-Policy, Location" Header always set Access-Control-Max-Age "600" </Location> # so läuft mal: http://www.guli.com <Location "/email"> ProxyPass "http://localhost:50099/email" ProxyPassReverse "http://localhost:50099/email" ProxyPassReverseCookiePath "/email" "/" RequestHeader set X-Forwarded-Prefix "/email" ProxyPreserveHost On </Location> # /ta/swagger-ui.html <Location "/ta"> ProxyPass "http://localhost:50086/ta" ProxyPassReverse "http://localhost:50086/ta" ProxyPassReverseCookiePath "/ta" "/" RequestHeader set X-Forwarded-Prefix "/ta" ProxyPreserveHost On </Location> # pres/swagger-ui.html <Location "/pres"> ProxyPass "http://localhost:50083/pres" ProxyPassReverse "http://localhost:50083/pres" ProxyPassReverseCookiePath "/pres" "/" RequestHeader set X-Forwarded-Prefix "/pres" ProxyPreserveHost On </Location> <Location "/guli-web"> ProxyPass "http://localhost:50096/guliadmin-web" connectiontimeout=5 timeout=600 ProxyPassReverse "http://localhost:50096/guliadmin-web" ProxyPassReverseCookiePath "/guliadmin-web" "/guliadmin-web" ProxyPreserveHost On </Location> <Location "/.well-known"> ProxyPass "!" </Location> ProxyRequests Off ProxyVia Off ProxyStatus On </IfModule>
问题解答
一、常见触发403状态码的请求特征
403本质是服务器识别到请求但拒绝放行,不同组件触发的常见场景如下:
- Apache层触发:
- 配置了IP黑白名单,来源IP被拦截
- 目录访问权限配置错误,请求路径无访问授权
- 跨域配置不匹配,预检OPTIONS请求被拦截
- WAF/安全模块识别请求包含恶意特征(比如SQL注入、XSS特征参数)
- 请求方法被显式禁止
- Tomcat层触发:
- 启用了CSRF防护,POST请求未携带合法CSRF令牌
- 请求头/请求体大小超过配置阈值
- 配置了虚拟路径访问权限限制
- 内置CORS过滤器拦截非法跨域请求
- 特殊请求方法被禁止
- SpringBoot层触发(无Spring Security场景):
- 自定义拦截器/过滤器返回403(比如权限校验、签名校验不通过)
- CORS全局配置不匹配拦截请求
- 静态资源路径权限配置错误
- 请求方法与接口定义不匹配(多数场景返回405,部分自定义配置会返回403)
二、403返回组件定位方法
按从外到内的顺序逐层排查,效率最高:
- 绕过Apache直接访问Tomcat端口测试
在服务器本地执行curl命令直接请求Tomcat对应的服务端口,比如curl -X POST http://localhost:50080/main/你的接口路径 -d '请求参数' -i,如果直接访问Tomcat返回403,说明问题出在Tomcat或SpringBoot层;如果直接访问正常,说明403是Apache层返回。 - 查看Apache日志
Apache默认日志路径在/var/log/httpd/或/apache/logs/下,查看对应时间的access_log和error_log,如果日志中记录了该请求的403响应,且有对应模块拦截的报错信息(比如mod_security、mod_access_compat的拦截记录),即可确认是Apache返回。 - 区分Tomcat和SpringBoot返回
如果绕过Apache访问仍然返回403,查看Tomcat的localhost_access_log和catalina.out日志:如果Tomcat日志记录该请求已经转发到应用,且应用日志有对应请求的打印,说明是SpringBoot层自定义逻辑返回;如果Tomcat日志没有请求转发到应用的记录,且有对应拦截报错,说明是Tomcat层返回。也可以通过响应头特征判断:Tomcat返回的403响应头通常带有Server: Apache-Coyote/1.1标识,SpringBoot自定义返回的响应头通常带有应用自定义Header。
三、结合当前配置的高频问题点与修复
从现有配置看,最高发的原因有三个:
- 跨域预检请求未放行
你在Apache根路径配置了CORS规则,但没有对OPTIONS请求做放行处理。浏览器发送跨域POST请求时会先发OPTIONS预检请求,如果Apache没有给OPTIONS请求返回200,就会直接触发403。
修复方法:在<Location "/">配置块中添加OPTIONS请求放行规则:
配置完成后重启Apache即可。RewriteEngine On RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ $1 [R=200,L] - Tomcat CSRF防护拦截
如果是外部Tomcat部署,且web.xml中配置了CsrfPreventionFilter,会拦截未携带合法CSRF令牌的POST请求。
修复方法:不需要CSRF防护就直接在Tomcat的web.xml中移除CsrfPreventionFilter配置;需要保留的话,前端请求时携带从Cookie中获取的CSRF令牌即可。 - 代理路径斜杠匹配问题
根路径代理配置转发到http://localhost:50080/main/带末尾斜杠,如果POST请求路径拼接后不符合Tomcat端的上下文路径,部分Tomcat版本的路径校验规则也会返回403,可以尝试去掉ProxyPass和ProxyPassReverse的末尾斜杠测试:ProxyPass "http://localhost:50080/main" connectiontimeout=5 timeout=600 ProxyPassReverse "http://localhost:50080/main"
内容的提问来源于stack exchange,提问作者user2023141
相关产品推荐
相关产品推荐

