同源策略
同源策略就是 从源a向源b发送请求,如果源a和源b的协议域名端口 不同的话,请求能成功发出,但是响应会被浏览器拦截。
跨域请求 本身不是问题。但是必须得到 服务器(资源提供方)的同意。
跨域是
浏览器和服务器之间的问题。 服务器和服务器之间不存在这个问题
解决跨域的方式
cors 跨域资源共享
CORS的核心思想是:跨域请求的安全性应该由被请求的资源方(服务器)来决定,而不是请求方。
CORS 的工作机制:两种请求类型
CORS将跨域请求分为两类:简单请求 和 非简单请求。浏览器处理这两种请求的方式完全不同。
3.1 简单请求 (Simple Request)
如果一个请求同时满足以下所有条件,它就是一个“简单请求”:
-
请求方法是以下三者之一:
- GET
- POST
- HEAD
-
HTTP头部信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Content-Type(值仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain)
表示请求体的类型 - DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- 请求中没有使用 ReadableStream 对象。
简单请求的交互流程:
-
浏览器发送请求: 浏览器在
发送请求时,会自动在HTTP头部添加一个Origin字段,表明请求来自哪个源。GET /data HTTP/1.1 Host: api.service.com Origin: http://my-app.com -
服务器处理并响应:
服务器收到请求后,检查Origin头部。如果服务器允许这个源的请求,它会在响应头中加入Access-Control-Allow-Origin。HTTP/1.1 200 OK Access-Control-Allow-Origin: http://my-app.com Content-Type: application/json {"message": "Hello from API!"}如果服务器使用
通配符 *,则表示允许任何源的请求:Access-Control-Allow-Origin: * -
浏览器处理响应:
浏览器收到响应后,检查 Access-Control-Allow-Origin 头部。- 如果该头部的值包含了当前源
(http://my-app.com)或者为 *,浏览器就认为请求成功,将响应数据交给JavaScript。 - 如果
没有这个头部,或者头部的值不匹配,浏览器就会拦截响应,并在控制台抛出CORS错误。
- 如果该头部的值包含了当前源
3.2 非简单请求 (Preflighted Request)
不满足“简单请求”条件的请求,就是“非简单请求”。例如:
- 使用了 PUT, DELETE, PATCH 等方法。
- Content-Type 为 application/json。
- 请求头中包含了自定义头部,如 X-Token。
为什么需要“预检”?
这些“非简单”请求可能会对服务器数据产生 副作用(比如修改或删除数据)。在没有CORS的年代,服务器可能根本没想过会收到来自其他源的 DELETE 请求。如果浏览器直接发送,可能会造成意想不到的后果。
因此,为了安全,浏览器在发送真正的请求之前,会先发送一个“投石问路”的预检请求 (Preflight Request) 。
预检请求的交互流程(两步走):
第一步:预检请求 (Preflight Request)
-
浏览器发送OPTIONS请求: 浏览器使用 OPTIONS 方法,向服务器发送一个预检请求。这个请求不包含请求体,但包含了几个关键的头部:
- Origin: 表明请求来源。
- Access-Control-
Request-Method: 告知服务器,实际请求将使用哪种HTTP方法。 - Access-Control-
Request-Headers: 告知服务器,实际请求将携带哪些自定义头部。
OPTIONS /users/123 HTTP/1.1 Host: api.service.com Origin: http://my-app.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Token, Content-Type -
服务器响应预检请求: 服务器收到预检请求后,根据这些信息判断是否同意即将到来的实际请求。如果同意,它会在响应中返回一系列 Access-Control-* 头部,作为授权。
HTTP/1.1 204 No Content Access-Control-Allow-Origin: http://my-app.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: X-Token, Content-Type Access-Control-Max-Age: 86400- Access-Control-Allow-Methods: 允许的请求方法。
- Access-Control-Allow-Headers: 允许的请求头部。
- Access-Control-Max-Age: 预检请求的有效期(秒)。在此期间,浏览器无需为同样的请求再次发送预检。
第二步:实际请求 (Actual Request)
-
浏览器检查预检响应: 浏览器收到预检响应后,检查授权信息是否与即将发送的实际请求匹配。
如果匹配(允许的源、方法、头部都OK),浏览器就会发送真正的请求。这个请求的过程就和“简单请求”一样了。- 如果不匹配,浏览器会像处理简单请求失败一样,在控制台抛出CORS错误,并且不会发送实际请求。
-
服务器处理实际请求并响应: 流程同简单请求。服务器响应时,仍然需要包含 Access-Control-Allow-Origin 头部。
4. CORS 核心:HTTP 头部详解
请求头部 (Request Headers)
- Origin: (所有跨域请求都会带) 表明请求的来源。
- Access-Control-Request-Method: (仅预检请求) 告知服务器实际请求的方法。
- Access-Control-Request-Headers: (仅预检请求) 告知服务器实际请求携带的自定义头部。
响应头部 (Response Headers)
- Access-Control-Allow-Origin: (必需) 允许访问的源。可以是单个源,也可以是 *。
- Access-Control-Allow-Methods: (预检响应必需) 允许的方法列表,如 GET, POST, DELETE。
- Access-Control-Allow-Headers: (预检响应必需) 允许的头部列表。
- Access-Control-Allow-Credentials: (可选) 是否允许发送Cookie等凭证。值为 true 或 false。
- Access-Control-Expose-Headers: (可选) 默认情况下,浏览器JS只能获取到Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma这几个响应头。如果服务器想让客户端能访问其他头部(如自定义的 X-Pagination-Count),就必须在这里指定。
- Access-Control-Max-Age: (可选, 仅预检响应) 预检请求的缓存时间(秒)。
前端设置代理服务器 解决跨域
前端服务器 不仅提供前端页面。还提供api代理服务。 因为跨域是浏览器和服务器之间的问题。服务器和服务器之间没有这个问题。