导航栏网站建站,团购网站做不起来,注册深圳公司恒诚信价格,白城网站建设哪家专业CORS#xff08;跨域资源共享#xff09;使用额外的HTTP头部来告诉浏览器#xff0c;允许运行在origin(domain)上的Web应用访问来自不同源服务器上的指定资源。
浏览器访问一个web应用#xff0c;这个web应用会发很多的跨域请求#xff0c;例如加载不同源的JS/CSS脚本跨域资源共享使用额外的HTTP头部来告诉浏览器允许运行在origin(domain)上的Web应用访问来自不同源服务器上的指定资源。
浏览器访问一个web应用这个web应用会发很多的跨域请求例如加载不同源的JS/CSS脚本或者加载不同源图片等。但是并没有发现请求的异常这些资源是可以正常返回的。而通过JS发送的跨域HTTP请求却时常得到错误所以跨域请求很常见但是浏览器对于请求跨域的限制却只存在于脚本发送的HTTP请求Ajax/Fetch。
同源限制在安全上是有其必要性的例如可以很轻松规避CSRF攻击。
通过上面的描述可以看出来同源策略的限制存在于浏览器端而CORS策略用额外的HTTP请求头字段来告诉浏览器该资源是允许被当前域上的web应用跨域访问的。
CORS的具体步骤
那么具体CORS是怎么做的呢
浏览器察觉请求跨域没有发起请求浏览器发起跨域请求没有正常返回结果
上面说到“通过额外的HTTP头告诉浏览器源上的web应用被允许访问来自不同源服务器上的指定资源”。告诉浏览器的自然是通过服务端的响应携带的额外的HTTP头所以可以看出来请求是发送了的。那么服务端是携带了标识当前源允许访问的该资源的HTTP头如果允许的话自然是请求一切正常不允许的话浏览器则会报错并且不会将请求结果返回给请求的发起方也就是Ajax/Fetch代码并且代码中获取不到是哪一步出了错只能在浏览器中看到错误日志为了安全。
那么就只是这样吗跨域请求失败是因为浏览器正常发送了请求服务端正常响应了请求然后浏览器发觉响应头中没有允许跨域的标识然后拦截返回结果并报错。
并不完全是这样这只是CORS的一部分这部分被称为简单请求。
既然有简单请求就有非简单请求。非简单请求的具体步骤和上面描述的简单请求很不一样会在发送真正的请求之前发送一个预检请求preflight request询问服务端是否允许当前源跨域访问该资源允许则继续发送真正的请求否则直接报错。
所以上面的两种做法都被应用到CORS策略中一种是拦截请求的返回结果一种是不发送真正的跨域请求。
简单请求
简单请求并不会发送预检请求而是直接发送真正的请求。简单请求必须要全部满足下面的条件
使用下列方法之一 GETHEADPOST 不得人为设置该集合之外的其他首部字段。该集合为 AccepAccept-LanguageContent-LanguageContent-Type需要注意额外的限制DPRDownlinkSave-DataViewport-WidthWidth Content-Type 的值仅限于下列三者之一这个集合中没有application/json text/plainmultipart/form-dataapplication/x-www-form-urlencoded 请求中的任意XMLHttpRequestUpload对象均没有注册任何事件监听器XMLHttpRequestUpload对象可以使用 XMLHttpRequest.upload 属性访问。请求中没有使用ReadableStream对象。
下面我们将从源http://dev.jd.com:9091访问http://dev.jd.com:9090的资源。
如果没有使用CORS显然会被浏览器的同源策略限制从而报错如下 分别查看请求头
GET /corsget HTTP/1.1
Host: dev.jd.com:9090
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1
Accept: */*
Origin: http://dev.jd.com:9091
Referer: http://dev.jd.com:9091/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q0.9,en;q0.8通过请求头可以看出来其中包含了很多简单请求定义的字段以外的字段但是却并没有触发预检请求这是因为这些头字段并不是人为设置的。
响应头
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charsetutf-8
Content-Length: 16
ETag: W/10-oV4hJxRVSENxc/wX8mA4/Pe4tA
Date: Mon, 06 Apr 2020 09:45:36 GMT
Connection: keep-alive修改服务端程序让响应头带上标识告诉浏览器允许源http://dev.jd.com:9091上的web应用访问不同源http://dev.jd.com:9090上的资源/corsget。
请求头同上响应头如下
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json; charsetutf-8
Content-Length: 16
ETag: W/10-oV4hJxRVSENxc/wX8mA4/Pe4tA
Date: Mon, 06 Apr 2020 09:58:06 GMT
Connection: keep-alive可以看到多了一个Access-Control-Allow-Origin这个字段该字段表示被允许访问该资源的不同源这里指定了*表示告诉浏览器任何源都可以访问该资源。
通过观察还可以发现请求头中有字段Origin正好对应的是就是http://dev.jd.com:9091web应用所在的源。 非简单请求
上面简单请求的条件只要有一个没有满足就会变成非简单请求。非简单请求会首先发送一个预检请求询问服务端是否允许跨域服务端允许后才会发送真正的请求。
注chrome的network面板中看不到预检请求可以查看 chrome://flags/#out-of-blink-cors 配置改成 disabled 后重启 Chrome 或者换个浏览器Firefox是可以看到的目前是74.0版本。
预检的请求头
OPTIONS /corsget HTTP/1.1
Host: dev.jd.com:9090
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Origin: http://dev.jd.com:9091
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1
Accept: */*
Referer: http://dev.jd.com:9091/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q0.9,en;q0.8通过人为修改请求头的content-type为application/json将原本的简单请求变成了非简单请求因为application/json并不在简单请求的三种content-type中。
这样就需要首先发送一个预检请求。
可以看到请求头中有如下字段 Access-Control-Request-Method: GET 用于预检请求将实际发送的请求的method告知服务器 Access-Control-Request-Headers: content-type 用于预检请求将实际发送的请求头不满足简单请求条件告知给服务器 Origin: http://dev.jd.com:9091 告诉服务器请求源
预检响应头
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: OPTIONS
Access-Control-Allow-Headers: Content-Type
Content-Type: text/html; charsetutf-8
Content-Length: 0
ETag: W/0-2jmj7l5rSw0yVb/vlWAYkK/YBwk
Date: Mon, 06 Apr 2020 10:57:40 GMT
Connection: keep-alive可以看到如下字段 Access-Control-Allow-Origin: * 告诉浏览器任何源都可访问该资源 Access-Control-Allow-Methods: OPTIONS 告诉浏览器options被允许跨域访问该资源。options并不在简单请求被允许的method中但是预检请求确是options所以需要指定options被允许。 Access-Control-Allow-Headers: Content-Type 告诉浏览器三个Content-Type值之外的Content-Type值被允许跨域访问该资源。因为application/json并不在简单请求的三个content-type中。
然后发送实际的get请求请求头如下
GET /corsget HTTP/1.1
Host: dev.jd.com:9090
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Origin: http://dev.jd.com:9091
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1
Content-Type: application/json
Accept: */*
Referer: http://dev.jd.com:9091/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q0.9,en;q0.8实际请求的响应头如下
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json; charsetutf-8
Content-Length: 16
ETag: W/10-oV4hJxRVSENxc/wX8mA4/Pe4tA
Date: Mon, 06 Apr 2020 10:57:40 GMT
Connection: keep-alive可以看到实际请求的响应头中并没有复杂的访问控制类型的HTTP头只有一个Access-Control-Allow-Origin: *。在实际请求的响应头中这个字段是必须的如果没有这个头部即使预检通过了实际发送的请求还是会失败返回的结果还是会被浏览器拦截并不会返回给脚本并报错。 跨域请求和凭证
Ajax和Fetch的跨域请求默认不会携带凭证。可以通过Ajax/Fetch的设置让发送请求的时候带上凭证但是如果服务端并不允许携带凭证的跨域请求那么理所当然的跨域请求会失败。 Ajax: xhr.withCredentials true
Fetch: fetch(url, {credentials: include, mode: cors})
当服务端设置 Access-Control-Allow-Credentials: true 允许跨域请求携带凭证的时候对于Access-Control-Allow-Origin还有一个限制就是值不能为*。 那么我们就需要在后端设置上指定允许携带凭证跨域的源如果有多个怎么办呢因为Access-Control-Allow-Origin这个字段并不能设置多个值可以通过代码获取请求头的Origin来判断是否允许获取该资源允许的话将Origin值设置给响应的HTTP头字段Access-Control-Allow-Origin即可。 仔细观察就可以发现Access-Control-Allow-Credentials: true这个响应头在预检和实际请求的响应头中被返回了两次一次都不能少否则会报错一样是跨域错误但是之前设置的Access-Control-Allow-Headers: Content-Type只有在预检的时候才会被返回。
其实关于Access-Control的HTTP头还有一个是控制预检请求的缓存时间的就是Access-Control-Max-Age单位是秒。
所以初步猜测Access-Control-Allow-Credentials这个响应头并不能被缓存
问题
脚本会发送跨域请求Origin指向的是HTML所在源还是脚本所在源呢
参考
HTTP访问控制CORSServer-Side Access ControlFetch