php方案 PHP的跨域资源共享(CORS)深度

📅 发布时间:2026/7/8 11:54:59 👁️ 浏览次数:
php方案 PHP的跨域资源共享(CORS)深度
PHP的跨域资源共享CORS深度 先说问题 浏览器安全策略A域名的JS不能请求B域名的API前端 http://app.com → 请求 → http://api.com/users ← 浏览器拦截CORS服务器告诉浏览器我允许你来---浏览器的两种请求 简单请求GET/POST普通Header → 直接发服务器响应头控制 预检请求PUT/DELETE/自定义Header → 先发OPTIONS问一句我能来吗服务器回答后才发真正请求---1.基础实现classCorsMiddleware{privatearray$config[origins[https://app.com,https://admin.com],methods[GET,POST,PUT,DELETE,OPTIONS],headers[Content-Type,Authorization,X-Request-ID],max_age86400,// 预检结果缓存24小时credentialstrue,];publicfunctionhandle(Request$req,callable$next):Response{$origin$req-header(Origin);// 不在白名单不加CORS头浏览器会拦截if(!in_array($origin,$this-config[origins])){return$next($req);}// 预检请求直接返回不走业务逻辑if($req-method()OPTIONS){return$this-preflight($origin);}$response$next($req);return$this-addHeaders($response,$origin);}privatefunctionpreflight(string$origin):Response{returnresponse(204,,[Access-Control-Allow-Origin$origin,Access-Control-Allow-Methodsimplode(,,$this-config[methods]),Access-Control-Allow-Headersimplode(,,$this-config[headers]),Access-Control-Max-Age$this-config[max_age],Access-Control-Allow-Credentialstrue,]);}privatefunctionaddHeaders(Response$res,string$origin):Response{return$res-withHeaders([Access-Control-Allow-Origin$origin,// 不能用*否则credentials失效Access-Control-Allow-Credentialstrue,VaryOrigin,// 告诉CDN按Origin缓存]);}}---2.关键细节 通配符*的坑// 错误带cookie/Authorization时* 不生效header(Access-Control-Allow-Origin: *);header(Access-Control-Allow-Credentials: true);// 浏览器直接报错// 正确动态反射Origin白名单验证后$origin$_SERVER[HTTP_ORIGIN]??;if(in_array($origin,$allowedOrigins)){header(Access-Control-Allow-Origin:$origin);header(Access-Control-Allow-Credentials: true);header(Vary: Origin);// 必须加否则CDN缓存串了}---暴露自定义响应头// 默认浏览器只能读6个安全响应头// 自定义头必须显式暴露header(Access-Control-Expose-Headers: X-Request-ID, X-RateLimit-Remaining);// 前端才能读到// response.headers.get(X-Request-ID) // ✓---带凭证请求CookieAuthorization// 服务端header(Access-Control-Allow-Origin: https://app.com);// 必须指定不能*header(Access-Control-Allow-Credentials: true);// 前端必须配合fetch(https://api.com/user,{credentials:include// 带cookie});// axiosaxios.defaults.withCredentialstrue;---3.动态白名单多租户场景classDynamicCorsHandler{publicfunctiongetAllowedOrigin(string$origin):?string{// 精确匹配if(in_array($origin,$this-staticWhitelist))return$origin;// 子域名通配*.company.com$hostparse_url($origin,PHP_URL_HOST);if(preg_match(/^[\w-]\.company\.com$/,$host))return$origin;// 数据库动态白名单SaaS多租户$tenant$this-db-query(SELECT id FROM tenants WHERE origin? AND active1,[$origin])-fetch();return$tenant?$origin:null;}}---4.安全陷阱// ✗ 危险不验证直接反射header(Access-Control-Allow-Origin:{$_SERVER[HTTP_ORIGIN]});// ✗ 危险正则太宽松preg_match(/company\.com/,$origin)// evil-company.com 也能通过// ✓ 正确精确白名单 或 严格正则preg_match(/^https:\/\/([\w-]\.)?company\.com$/,$origin)// ✗ 危险OPTIONS不拦截就直接返回绕过认证if($methodOPTIONS){addCorsHeaders();exit;// 忘了验证TokenOPTIONS请求直接过了}// ✓ 正确OPTIONS只返回CORS头不泄露业务数据// 认证中间件在CORS中间件之后执行---5.Nginx 层处理推荐# 不走PHP性能更好location/api/{set$cors_origin;if($http_origin~*^https://(app|admin)\.company\.com$){set$cors_origin$http_origin;}if($request_methodOPTIONS){add_header Access-Control-Allow-Origin$cors_originalways;add_header Access-Control-Allow-MethodsGET,POST,PUT,DELETEalways;add_header Access-Control-Allow-HeadersAuthorization,Content-Typealways;add_header Access-Control-Max-Age86400always;return204;}add_header Access-Control-Allow-Origin$cors_originalways;add_header Access-Control-Allow-Credentialstruealways;add_header Vary Origin always;}---中间件执行顺序 请求进来 ↓CORS中间件 ← 最先执行OPTIONS直接返回 ↓ 限流中间件 ↓ 认证中间件 ← 验证Token/Cookie ↓ 业务逻辑---全景图 浏览器PHP服务器 │ │ │──OPTIONS/api/user ──────────│ 预检我能发DELETEAuthorization吗 │─204Allow-Origin/Methods ───│ 回答能缓存24小时 │ │ │──DELETE/api/user ───────────│ 真正请求 │ Authorization:Bearer xxx │ │─200Allow-Origin ─────────│ 响应带CORS头浏览器放行 关键头 请求方 Origin → 响应方 ← Access-Control-Allow-Origin必须 ← Access-Control-Allow-Credentials带cookie时 ← Vary:OriginCDN缓存隔离---一句话CORS服务器白名单校验Origin预检OPTIONS返回许可响应头告知浏览器放行带凭证时禁用*必须动态反射Vary:Origin防CDN缓存串。