CORS 不是身份认证
目标
用真实的预检请求来验证源、方法和请求头的允许矩阵。
为什么重要
前端读不到响应,于是对所有源都用通配符放行。在发送 Cookie 的请求中,策略变得更复杂,而开发者又误以为只要打开 CORS,外部请求就会被拦截。本实验把浏览器的读取策略和服务器认证分开。它不会替你实现认证功能。
步骤
- 在
/root/work/fa-cors-policy-lab/service.py中,origin(value) 在它是 http 或 https 的 URL,有 host,且没有 path、query、fragment 和用户信息时,返回输入字符串。其余都是 ValueError。末尾的 / 也属于 path,所以要拒绝。
先做一次准备。已有的文件不会被覆盖。
mkdir -p /root/work/fa-cors-policy-lab
test -e /root/work/fa-cors-policy-lab/service.py || cp /opt/fixtures/ten_labs/fa-cors-policy-lab/service.py /root/work/fa-cors-policy-lab/service.py
cd /root/work/fa-cors-policy-lab
-
在
/root/work/fa-cors-policy-lab/service.py中,origins(values) 先用 origin 验证每一项,再按首次出现的顺序去除重复,得到新列表。 -
在
/root/work/fa-cors-policy-lab/service.py中,methods(values) 只允许 GET、POST、PUT、DELETE、OPTIONS,并转成大写后去除重复。空列表或其他值都是 ValueError。 -
在
/root/work/fa-cors-policy-lab/service.py中,policy(allowed, credentials) 检查 credentials 是否是 bool。如果 allowed 中有 '*',就是 ValueError,并返回 {allow_origins:origins(allowed), allow_credentials:credentials}。 -
在
/root/work/fa-cors-policy-lab/service.py中,create_app(allowed, credentials=True) 是验证了 policy 并设置了 CORSMiddleware 的应用。它只允许 GET/POST,允许 Content-Type 和 X-Request-ID 请求头,并暴露(expose)X-Trace 响应头。GET /data 返回 {ok:True},以及 X-Trace='trace-1'。 -
在
/root/work/fa-cors-policy-lab/service.py中,preflight_headers(source, method, requested='X-Request-ID') 是一个带有 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 三个键的字典。method 是大写。 -
在
/root/work/fa-cors-policy-lab/service.py中,preflight_status(app, source, method, requested='X-Request-ID') 用 TestClient 向 /data 发送 OPTIONS 请求,并返回 HTTP 状态。其他源、DELETE、X-Secret 头都必须是 400。 -
在
/root/work/fa-cors-policy-lab/service.py中,cors_observation(app, source) 发送 GET /data,并返回(状态,Access-Control-Allow-Origin 的值或 None,JSON 正文)。即使是不被允许的源,200 的正文也会被执行,但不应带有允许源的头。
参考
- 不联网、不安装软件包,在现有的 lab-dev 环境中进行。
- 每一步都在 45 秒的评分预算内运行。不要添加真实的 sleep 或网络调用。
- 评分会重新导入提交的模块,并用独立的输入和临时 DB 进行检查。请实现契约,而不要把预期值当作常量返回。
- FastAPI 官方文档 · pytest 官方文档 · Python sqlite3
- 局限:TestClient 不是浏览器。它检查 CORS 响应头和预检,但并不会实现浏览器自身的读取拦截。带着不被允许的 Origin 发送的普通 GET,也可能在服务器上被执行。敏感操作必须用单独的认证、权限和 CSRF 策略来保护。
验证源的格式
在 /root/work/fa-cors-policy-lab/service.py 中,origin(value) 在它是 http 或 https 的 URL,有 host,且没有 path、query、fragment 和用户信息时,返回输入字符串。其余都是 ValueError。末尾的 / 也属于 path,所以要拒绝。
先做一次准备。已有的文件不会被覆盖。
mkdir -p /root/work/fa-cors-policy-lab
test -e /root/work/fa-cors-policy-lab/service.py || cp /opt/fixtures/ten_labs/fa-cors-policy-lab/service.py /root/work/fa-cors-policy-lab/service.py
cd /root/work/fa-cors-policy-lab
如果把整个 URL 都当作源允许,就可能把路径或用户信息混淆。
保存后用 bash /opt/lab/checks/fa-cors-policy-lab/01-contract.sh 确认。
去除重复的源
在 /root/work/fa-cors-policy-lab/service.py 中,origins(values) 先用 origin 验证每一项,再按首次出现的顺序去除重复,得到新列表。
白名单是精确的源列表,而不是字符串的部分匹配。
保存后用 bash /opt/lab/checks/fa-cors-policy-lab/02-contract.sh 确认。
用白名单限制方法
在 /root/work/fa-cors-policy-lab/service.py 中,methods(values) 只允许 GET、POST、PUT、DELETE、OPTIONS,并转成大写后去除重复。空列表或其他值都是 ValueError。
不要悄悄加入没有被允许的 PATCH 和任意方法。
保存后用 bash /opt/lab/checks/fa-cors-policy-lab/03-contract.sh 确认。
不同时允许凭据和通配符
在 /root/work/fa-cors-policy-lab/service.py 中,policy(allowed, credentials) 检查 credentials 是否是 bool。如果 allowed 中有 '*',就是 ValueError,并返回 {allow_origins:origins(allowed), allow_credentials:credentials}。
本实验的明确策略是,无论是否允许凭据,都不接受通配符。
保存后用 bash /opt/lab/checks/fa-cors-policy-lab/04-contract.sh 确认。
挂上真实的 CORS 中间件
在 /root/work/fa-cors-policy-lab/service.py 中,create_app(allowed, credentials=True) 是验证了 policy 并设置了 CORSMiddleware 的应用。它只允许 GET/POST,允许 Content-Type 和 X-Request-ID 请求头,并暴露(expose)X-Trace 响应头。GET /data 返回 {ok:True},以及 X-Trace='trace-1'。
如果在 preflight 和实际响应中手动分别添加头,两套策略很容易产生偏差。
保存后用 bash /opt/lab/checks/fa-cors-policy-lab/05-contract.sh 确认。
构造预检请求
在 /root/work/fa-cors-policy-lab/service.py 中,preflight_headers(source, method, requested='X-Request-ID') 是一个带有 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 三个键的字典。method 是大写。
实际的请求方法是 OPTIONS,要检查的方法在另一个头里。
保存后用 bash /opt/lab/checks/fa-cors-policy-lab/06-contract.sh 确认。
计算拒绝矩阵
在 /root/work/fa-cors-policy-lab/service.py 中,preflight_status(app, source, method, requested='X-Request-ID') 用 TestClient 向 /data 发送 OPTIONS 请求,并返回 HTTP 状态。其他源、DELETE、X-Secret 头都必须是 400。
不要把三种拒绝原因混在同一个请求里,才能找出缺失的策略。
保存后用 bash /opt/lab/checks/fa-cors-policy-lab/07-contract.sh 确认。
观察 CORS 与认证的区别
在 /root/work/fa-cors-policy-lab/service.py 中,cors_observation(app, source) 发送 GET /data,并返回(状态,Access-Control-Allow-Origin 的值或 None,JSON 正文)。即使是不被允许的源,200 的正文也会被执行,但不应带有允许源的头。
curl 或服务器之间的请求不遵守浏览器的 CORS 读取限制。
保存后用 bash /opt/lab/checks/fa-cors-policy-lab/08-contract.sh 确认。