🌐 Language: English Version | 中文版
TL;DR
Problem: OAuth2 redirect URL contains incorrect port http://example.com:9080
Root Cause: APISIX trusted_addresses configuration too restrictive, causing X-Forwarded-* headers to be overwritten
Solution: Add upstream proxy network segments to trusted_addresses
Problem Background
In a typical multi-layer proxy architecture:
flowchart TD
Browser[Browser
HTTPS:443] --> Nginx[Nginx
TLS Termination]
Nginx --> APISIX[APISIX
Docker:9080]
APISIX --> OAuth2[Spring OAuth2
Authorization Server]
Users access the system via https://public.example.com. When not logged in, accessing the OAuth2 authorization endpoint /oauth2/authorize should theoretically return:
1 | 302 Found |
But the actual response was:
1 | 302 Found |
When the browser accesses https://public.example.com:9080/login, it throws ERR_CONNECTION_CLOSED, preventing users from logging in.
Problem Analysis
Browser Network Request Chain
1 | GET https://public.example.com/oauth2/authorize → 302 |
Key Application Log Information
1 | requestUrl=http://public.example.com:9080/oauth2/authorize |
From the logs, we can see Spring Security already sees the wrong request view:
- scheme recognized as
http - port recognized as
9080 - SavedRequest URL is corrupted
Troubleshooting Directions
Initially, we suspected several directions:
- Spring Security’s SavedRequest logic had issues
- APISIX’s proxy-rewrite plugin configuration was incorrect
- Application didn’t properly enable forward-headers-strategy
- Entry Nginx wasn’t passing X-Forwarded-* headers
These hypotheses could explain some symptoms, but none were the root cause.
Troubleshooting Timeline
| Phase | Hypothesis | Verification Method | Conclusion |
|---|---|---|---|
| 1 | Spring Security issue | Check SavedRequest logic | ❌ Ruled out |
| 2 | proxy-rewrite config | Compare plugin configs | ❌ Ruled out |
| 3 | forward-headers-strategy | Check app configuration | ❌ Ruled out |
| 4 | Upstream Nginx config | Check X-Forwarded-* passing | ❌ Ruled out |
| 5 | APISIX log diagnostics | Add diagnostic logs | ✅ Root cause identified |
Converging on APISIX
The real breakthrough came after adding diagnostic logs to APISIX, revealing:
1 | { |
Key findings:
- The incorrect
http/9080was already formed inside APISIX - It wasn’t the application “breaking it again” during the return phase
- The wrong request view the application received was passed in by the gateway itself
Easily Confused Points
During troubleshooting, the most confusing aspect was mixing “user real IP” chain with “public origin” chain. These are not the same thing:
User Real IP Chain
Used for risk control, auditing, rate limiting, and log location, typically relying on:
- X-Real-IP
- X-Forwarded-For
Public Origin Chain
Used for OAuth2 redirects, absolute URL generation, and callback address validation, typically relying on:
- X-Forwarded-Proto
- X-Forwarded-Host
- X-Forwarded-Port
The essence of this problem wasn’t “user IP not being passed through,” but APISIX corrupting the second chain after trust judgment failed.
APISIX Source Code Analysis
The problem ultimately converged on the trusted_addresses configuration.
APISIX request processing logic:
- Calculate previous hop address (usually via X-Real-IP or Remote Addr)
- Match this address against
apisix.trusted_addresses - If not trusted, overwrite:
- X-Forwarded-Proto
- X-Forwarded-Host
- X-Forwarded-Port
- Overwrite values are not the external public entry, but what APISIX observes:
- scheme
- host
- server_port
Pseudocode representation:
1 | -- Check if previous hop address is in trusted list |
This explains why the application kept seeing http and 9080:
- Once APISIX judges the upstream proxy as “not trusted”
- It downgrades external origin information to its own listening view
For more configuration details, refer to APISIX Official Documentation - trusted_addresses
Solution
Quick Verification
The simplest control variable experiment:
1 | apisix: |
After reloading, verify the OAuth2 authorization endpoint:
1 | Location: https://public.example.com/login |
The problem disappears immediately. This experiment itself isn’t the final configuration, but it pins down the root cause: the problem is at the APISIX trusted_addresses layer.
Production Configuration
More appropriate approach for production:
1 | apisix: |
With upstream Nginx configuration:
1 | location / { |
Why Application Layer Patches Aren’t the Root Solution
During troubleshooting, several workarounds were attempted at the application layer:
- Custom RequestCache, forcibly changing SavedRequest to public address
- Custom login redirect URL generation
- Wrapping HttpServletRequest in filters to override scheme/host/port
These methods can alleviate symptoms in the short term, but aren’t suitable as formal solutions:
- The problem won’t appear in just one service
- Absolute URL generation doesn’t only occur in OAuth2 login flows
- Future Swagger, email links, frontend API base URLs, callback addresses, and download links may encounter similar issues
If the public access view isn’t uniformly governed at the gateway layer, it will eventually become patchwork for each service.
Differences Between Dev and Production Environments
Another reality of this problem: development and production network topologies differ.
Common dev environment chain:
1 | Host Nginx → Docker Desktop port mapping → APISIX container |
More common production pattern:
1 | Nginx node → Internal network → APISIX node |
In both environments, the “previous hop address” that APISIX actually sees can be completely different.
This causes a phenomenon:
- In dev environments, using a very narrow
trusted_addressesmay not stably reproduce production behavior - Because the address object APISIX uses for trust judgment may not be the expected upstream proxy address
This is why in some local environments, temporarily widening trusted_addresses immediately restores functionality, while continuing to guess CIDRs can lead to endless loops.
Applicable Scenarios
This record applies to similar scenarios:
- APISIX deployed behind a reverse proxy
- Public unified domain and TLS termination at upstream layer
- Downstream applications generate absolute URLs based on scheme/host/port
- OAuth2 / OIDC login flows sensitive to URL view consistency
If any of the following phenomena appear in the chain:
- 302 Location contains internal port
- SavedRequest shows internal addresses or container ports
- Frontend callbacks or logout addresses mix in internal ports
- Mixed Content points to internal HTTP services
It’s worth checking if APISIX is overwriting X-Forwarded-*.
Quick Checklist
When encountering similar issues, check in order:
- Is
server_portin APISIX logs showing the internal port - Does
trusted_addressesinclude upstream proxy IPs - Is upstream Nginx properly setting X-Forwarded-* headers
- Is application enabled
forward-headers-strategy - Is OAuth2 redirect URL using internal addresses
For more on Forwarded Headers configuration, refer to Spring Security Official Documentation.
Summary
This problem isn’t essentially an OAuth2 protocol issue, nor is it Spring Security’s special behavior.
It’s more like a typical gateway trust boundary problem:
- External entry is HTTPS
- Gateway internal is HTTP 9080
- If the gateway doesn’t stably preserve the public access view
- Downstream services will naturally expose internal ports in absolute URLs
And APISIX’s trusted_addresses is the key switch in this chain.
The problem wasn’t solved by a single “magic configuration,” but through a complete convergence:
- First admitting application patches aren’t the root solution
- Then converging the problem to the gateway
- Then using source code and control variables to pin down the root cause
- Finally distinguishing dev environment temporary recovery from production formal configuration principles
If the runtime chain contains both upstream proxies, gateways, and applications that need to generate absolute URLs, this type of problem is worth prioritizing from the trust boundary rather than business code.
These solutions can help maintain trusted_addresses correctness in more complex environments, avoiding repeated issues due to IP changes.