CORS 是服务端的访问控制吗?预检请求长什么样?为什么带 cookie 的请求特别容易失败?这篇既用 test_client 看 Flask-CORS 发出的头,也用真实的无头 Chrome 与两个不同端口的服务,看浏览器到底拦了什么。
带 Origin: 200 {'Access-Control-Allow-Origin': 'http://app.example', 'Vary': 'Origin'}
不带 Origin: 200 {'Access-Control-Allow-Origin': '*'}上一篇:Flask-SocketIO 长连接;下一篇:自定义 Flask Extension。
200 {'Access-Control-Allow-Origin': 'http://app.example', 'Access-Control-Allow-Headers': 'content-type, x-token', 'Access-Control-Allow-Methods': 'DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT', 'Vary': 'Origin'} 正文长度 0
methods=[GET,POST],请求 PUT 的预检: 200 {} Origin=http://app.example → 200 {'Access-Control-Allow-Origin': 'http://app.example'} 正文={'ok': True}
Origin=http://evil.example → 200 {} 正文={'ok': True}
evil 的预检: 200 {} origins="*" + credentials → {'Access-Control-Allow-Origin': 'http://app.example', 'Access-Control-Allow-Credentials': 'true', 'Vary': 'Origin'}
origins=[A] + credentials → {'Access-Control-Allow-Origin': 'http://app.example', 'Access-Control-Allow-Credentials': 'true'} /api/x Origin=http://app.example → {'Access-Control-Allow-Origin': 'http://app.example', 'Access-Control-Expose-Headers': 'X-Total'}
/private Origin=http://app.example → {}
/one Origin=http://one.example → {'Access-Control-Allow-Origin': 'http://one.example'}
/one Origin=http://app.example → {} /boom → 500 {'Access-Control-Allow-Origin': 'http://app.example'}
/nonexistent → 404 {'Access-Control-Allow-Origin': 'http://app.example'}
/missing-abort → 403 {'Access-Control-Allow-Origin': 'http://app.example'}
TESTING=False 下 /boom: 500 {'Access-Control-Allow-Origin': 'http://app.example'} [接口没开 CORS]
GET /data FAIL TypeError: Failed to fetch
POST /data (JSON) FAIL TypeError: Failed to fetch
GET /data + X-Token 头 FAIL TypeError: Failed to fetch
GET /me (credentials: include) FAIL TypeError: Failed to fetch
服务端实际收到的请求: ['GET /data origin=http://127.0.0.1:9602 cookie=None', 'OPTIONS /data origin=http://127.0.0.1:9602 cookie=None', 'OPTIONS /data origin=http://127.0.0.1:9602 cookie=None', 'GET /me origin=http://127.0.0.1:9602 cookie=abc123']
[CORS(app) 默认配置]
GET /data OK 200 {"method":"GET","ok":true}
POST /data (JSON) OK 200 {"method":"POST","ok":true}
GET /data + X-Token 头 OK 200 {"method":"GET","ok":true}
GET /me (credentials: include) FAIL TypeError: Failed to fetch
服务端实际收到的请求: ['GET /data origin=http://127.0.0.1:9604 cookie=None', 'OPTIONS /data origin=http://127.0.0.1:9604 cookie=None', 'POST /data origin=http://127.0.0.1:9604 cookie=None ctype=application/json', 'OPTIONS /data origin=http://127.0.0.1:9604 cookie=None', 'GET /data origin=http://127.0.0.1:9604 cookie=None', 'GET /me origin=http://127.0.0.1:9604 cookie=abc123']
[CORS(app, origins=[页面源])]
GET /data OK 200 {"method":"GET","ok":true}
POST /data (JSON) OK 200 {"method":"POST","ok":true}
GET /data + X-Token 头 OK 200 {"method":"GET","ok":true}
GET /me (credentials: include) FAIL TypeError: Failed to fetch
服务端实际收到的请求: ['GET /data origin=http://127.0.0.1:9606 cookie=None', 'OPTIONS /data origin=http://127.0.0.1:9606 cookie=None', 'POST /data origin=http://127.0.0.1:9606 cookie=None ctype=application/json', 'OPTIONS /data origin=http://127.0.0.1:9606 cookie=None', 'GET /data origin=http://127.0.0.1:9606 cookie=None', 'GET /me origin=http://127.0.0.1:9606 cookie=abc123']
[origins=[页面源] + supports_credentials]
GET /data OK 200 {"method":"GET","ok":true}
POST /data (JSON) OK 200 {"method":"POST","ok":true}
GET /data + X-Token 头 OK 200 {"method":"GET","ok":true}
GET /me (credentials: include) OK 200 {"sid":"abc123"}
服务端实际收到的请求: ['GET /data origin=http://127.0.0.1:9608 cookie=None', 'OPTIONS /data origin=http://127.0.0.1:9608 cookie=None', 'POST /data origin=http://127.0.0.1:9608 cookie=None ctype=application/json', 'OPTIONS /data origin=http://127.0.0.1:9608 cookie=None', 'GET /data origin=http://127.0.0.1:9608 cookie=None', 'GET /me origin=http://127.0.0.1:9608 cookie=abc123']浏览器是本机的 Google Chrome 无头模式(--headless=new),页面与接口是两个不同端口的 gunicorn 服务。