Mastering CORS: Real‑World Backend Configurations and Chrome Private‑Network Fixes
This article shares a hands‑on journey of solving cross‑origin issues in a multi‑domain education product, covering CORS fundamentals, simple and preflight requests, Nginx and SpringBoot configurations, response‑code choices, and Chrome’s insecure private‑network restrictions, with practical solutions and lessons learned.
1. Encountering Cross‑Origin
During the incubation of an education product, the author, acting as an architect, faced a cross‑origin scenario where multiple front‑ends (institution, authority, parent portals) each had separate domains accessed via PC, WeChat, or QR‑code H5.
2. CORS Details
CORS (Cross‑Origin Resource Sharing) is a W3C standard that allows browsers to send XMLHttpRequest to a different origin, overcoming the same‑origin limitation.
Same origin means identical protocol, domain, and port.
When a user accesses http://admin.training.com and the API is at http://api.training.com, the request is cross‑origin.
2.1 Simple Request
If a request meets all of the following conditions, the browser sends a simple request; otherwise it performs a preflight request.
Method is GET, POST, or HEAD.
Only safe request headers are used:
Accept
Accept‑Language
Content‑Language
Content‑Type limited to text/plain, multipart/form-data, or application/x‑www‑form‑urlencoded HTML‑related headers such as DPR, Download, Save‑Data, Viewport‑Width, Width
No event listeners are registered on XMLHttpRequestUpload.
No ReadableStream is used in the request.
For a simple request the browser adds an Origin header and the server responds with Access‑Control‑Allow‑Origin and related headers.
Example response header allowing any origin: Access-Control-Allow-Origin: * If the server restricts to http://admin.training.com the header would be:
Access-Control-Allow-Origin: http://admin.training.com2.2 Preflight Request
For non‑simple requests the browser first sends an OPTIONS preflight request to ask the server whether the actual request is permitted.
The preflight request includes headers such as Access-Control-Request-Method and Access-Control-Request-Headers.
If the server approves, the browser proceeds with the actual request; otherwise it aborts.
3. Backend Configuration
Two stable approaches were tested over two months:
MND‑recommended Nginx configuration.
SpringBoot’s built‑in CorsFilter configuration.
▍MND‑Recommended Nginx Configuration
Configure CORS at the request‑forwarding layer:
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
}
}Using a wildcard for Access-Control-Allow-Headers simplifies handling custom headers such as signatures and tokens.
IE11 requires the header list to be comma‑separated; otherwise it reports “Access‑Control‑Allow‑Headers does not contain request header content‑type”.
▍SpringBoot Built‑In CorsFilter
Typical SpringBoot CORS setup:
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.allowCredentials(true)
.allowedHeaders("*")
.maxAge(3600);
}In practice the configuration did not take effect because an ActionInterceptor processed the request before the CORS filter, consuming the OPTIONS request and returning a JSON error.
Switching to a dedicated CorsFilter solved the ordering issue, as filters have the highest precedence.
private CorsConfiguration corsConfig() {
CorsConfiguration cors = new CorsConfiguration();
cors.addAllowedOrigin("*");
cors.addAllowedHeader("*");
cors.addAllowedMethod("*");
cors.setAllowCredentials(true);
cors.setMaxAge(3600L);
return cors;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig());
return new CorsFilter(source);
}When Access-Control-Allow-Headers is a wildcard, the filter echoes back the request’s Access-Control-Request-Headers as a comma‑separated list, satisfying IE11.
4. Preflight Response Code: 200 vs 204
Team members asked whether the preflight response should be 200 or 204. MDN originally showed 200; later it was updated to 204. In practice, 200 works everywhere, and 204 is also well‑supported by modern browsers.
5. Chrome: Insecure Private Network
During an internal demo, a page triggered a CORS error labeled InsecurePrivateNetwork after Chrome 94 introduced a new security feature.
Temporary fix: disable the flag chrome://flags/#block-insecure-private-network-requests and restart Chrome.
Root cause: the page accessed a private‑network IP (e.g., 172.16.x.x) from a public network, which Chrome blocks by default.
Official mitigation steps:
Serve private‑network resources over HTTPS.
Future browsers may require a header such as Access-Control-Request-Private-Network.
Work‑arounds include using another browser, disabling the flag, or mapping the host to an external IP.
6. Review
The API gateway (e.g., Meituan Shepherd, Tencent) proved valuable for unified domain management, authentication, rate‑limiting, and CORS handling, allowing backend services to stay agnostic of cross‑origin concerns.
The author’s mindset shifted from underestimating CORS to methodically understanding its mechanisms, which helped resolve the issues efficiently.
7. Final Thoughts
A story from a technical talk illustrated how a seemingly minor detail (e.g., a CLOSE_WAIT state) can pinpoint the root cause of a problem quickly. The same principle applies to CORS: paying attention to details leads to effective solutions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
