浏览器报 CORS 错误,服务端到底返回了什么?预检请求和真正的请求分别带哪些头?@CrossOrigin、addCorsMappings、CorsFilter 三个位置有什么区别?为什么 500 和 404 在浏览器里总是变成 CORS 错误?这篇在 Spring Boot 4.0.8 上用手工带 Origin 的请求逐个看响应头。
GET /plain [Origin: https://a.example] → 200 body='plain'
CORS 响应头:(没有)
GET /plain [] → 200 body='plain'
CORS 响应头:(没有)
OPTIONS /plain [Origin: https://a.example Req-Method: GET] → 200 body=''
CORS 响应头:(没有)服务端照常返回,拦住读取的是浏览器;CORS 不是访问控制。
GET /ann [Origin: https://a.example] → 200 body='ann'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://a.example'}
GET /ann [Origin: https://evil.example] → 403 body='Invalid CORS request'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers']}
GET /ann [] → 200 body='ann'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers']}
GET /ann [Origin: http://127.0.0.1:9602] → 200 body='ann'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers']}
OPTIONS /ann [Origin: https://a.example Req-Method: GET] → 200 body=''
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://a.example', 'Allow-Methods': 'GET', 'Max-Age': '1800'}
OPTIONS /ann [Origin: https://evil.example Req-Method: GET] → 403 body='Invalid CORS request'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers']}
OPTIONS /ann [Origin: https://a.example Req-Method: DELETE] → 200 body=''
CORS 响应头:(没有)白名单外的来源 403;预检与实际请求带的头不同;同源不算跨域。
OPTIONS /ann-put [Origin: https://a.example Req-Method: PUT Req-Headers: X-Token] → 200 body=''
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://a.example', 'Allow-Methods': 'GET,PUT', 'Allow-Headers': 'X-Token', 'Expose-Headers': 'X-Total', 'Allow-Credentials': 'true', 'Max-Age': '600'}
OPTIONS /ann-put [Origin: https://a.example Req-Method: PUT Req-Headers: X-Other] → 403 body='Invalid CORS request'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers']}
PUT /ann-put [Origin: https://a.example] → 200 body='ann-put'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://a.example', 'Expose-Headers': 'X-Total', 'Allow-Credentials': 'true'}
GET /open [Origin: https://any.example] → 200 body='open'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': '*'}
OPTIONS /open [Origin: https://any.example Req-Method: GET Req-Headers: X-Whatever] → 200 body=''
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': '*', 'Allow-Methods': 'GET', 'Allow-Headers': 'X-Whatever', 'Max-Age': '1800'}maxAge 默认 1800;空注解允许任意来源。
OPTIONS /api/x [Origin: https://b.example Req-Method: POST Req-Headers: Content-Type] → 200 body=''
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://b.example', 'Allow-Methods': 'GET,POST', 'Allow-Headers': 'Content-Type', 'Max-Age': '120'}
OPTIONS /api/x [Origin: https://b.example Req-Method: DELETE] → 403 body='Invalid CORS request'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers']}
GET /api/x [Origin: https://b.example] → 200 body='api-x'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://b.example'}
GET /plain [Origin: https://sub.example] → 200 body='plain'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://sub.example', 'Allow-Credentials': 'true'}
GET /plain [Origin: https://a.other] → 403 body='Invalid CORS request'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers']}
GET /ann [Origin: https://a.example] → 200 body='ann'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://a.example'}
GET /api/boom [Origin: https://b.example] → 500 body='{"status":500,"error":"Internal Server Error","pat'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://b.example'}allowedOriginPatterns 回显来源;注解仍然生效。
GET /boom [Origin: https://f.example] → 500 body='{"status":500,"error":"Internal Server Error","pat'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://f.example'}
GET /nope [Origin: https://f.example] → 404 body='{"status":404,"error":"Not Found","path":"/nope"}'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://f.example'}
OPTIONS /boom [Origin: https://f.example Req-Method: POST Req-Headers: X-Any] → 200 body=''
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://f.example', 'Allow-Methods': 'POST', 'Allow-Headers': 'X-Any'}
GET /boom [Origin: https://other.example] → 403 body='Invalid CORS request'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers']}在 DispatcherServlet 前,404 和异常响应都带头。
GET /boom [Origin: https://a.example] → 500 body='{"status":500,"error":"Internal Server Error","pat'
CORS 响应头:(没有)
GET /nope [Origin: https://a.example] → 404 body='{"status":404,"error":"Not Found","path":"/nope"}'
CORS 响应头:(没有)
GET /ann-boom [Origin: https://a.example] → 500 body='{"status":500,"error":"Internal Server Error","pat'
CORS 响应头:{'Vary': ['Origin', 'Access-Control-Request-Method', 'Access-Control-Request-Headers'], 'Allow-Origin': 'https://a.example'}没有匹配 CORS 配置的路径上,500 和 404 不带 CORS 头。
GET /plain [Origin: https://a.example] → 500 body='<!doctype html><html lang="en"><head><title>HTTP S'
CORS 响应头:(没有)
OPTIONS /plain [Origin: https://a.example Req-Method: GET] → 500 body='<!doctype html><html lang="en"><head><title>HTTP S'
CORS 响应头:(没有)
服务端日志:Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credeallowedOrigins("*") 加 allowCredentials(true) 启动不报错,请求时 500。