HTTP跨域与其共享机制
什么是跨域与其共享?
CORS全称为Cross-Origin Resource Sharing,被译为跨域资源共享,新增了一组HTTP首部字段,允许服务器声明哪些源站有权限访问哪些资源。
跨域资源共享(CORS)是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,此请求就是一个跨域 HTTP 请求
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求(或者是返回结果被浏览器拦截)。因为要确保后台传递过来数据的可靠性,就必须前后端关于CORS的设置是一致的,而解决此方法要么是后台放开跨域限制,要么是前端被动配合后台(这往往与业务不符合)。
跨域资源共享标准规范要求,对那些可能对服务器数据产生副作用的HTTP请求方法(特别是GET以外的HTTP请求,或者搭配某些MME类型的POST请求),浏览器必须首先使用OPTIONS方法发起一个预检请求,从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证。跨域资源共享机制的工作原理主要应用于三个场景∶
- 简单请求
- 预检请求
- 认证请求
简单请求
满足条件:
- 请求方法是以下三种方法之一: HEAD , GET , POST
- HTTP的头部信息不超过一下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
值得注意的:
这些跨域请求与浏览器发出的其他跨域请求并无二致。如果服务器未返回正确的响应首部,则请求方不会收到任何数据。因此,那些不允许跨域请求的网站无需为这一新的HTTP访问控制特性担心。
基本流程:
- 浏览器判断请求是简单请求,自动在头信息之中,添加一个
Origin
字段,包含了请求来自的源(协议+域名+端口); - 服务器根据这个值,决定是否同意这次请求(根据已有配置)。如果是在许可范围内,则返回正确HTTP响应并附带``Access-Control-Allow-Origin的`等头部字段信息,浏览器正常运行;如果不带这类字段,则浏览器会报错;
- 如果跨域被禁,抛出的错误会被
XMLHttpRequest对象的onerror函数
捕获,下同。
简单模式开放跨域基本代码
/**
* server.js
*/
const express = require("express");
var app = express();
app.use("/*",(req,res,next)=>{
res.header("Access-Control-Allow-Origin","*"); // 放开访问权限给所有地址
res.header("Access-Control-Allow-Headers","*"); // 这个也会匹配
res.header("Access-Control-Allow-Methods","PUT, POST, GET, DELETE, OPTIONS");// 这个也会匹配
res.header("X-Powered-By","3.2.1");
res.header("Content-Type","text/plain; charset=utf-8");
next();
});
app.post("/data",(req,res)=>{
res.send("回复消息了");
});
app.listen(8081,()=>{
console.log("listen on 8081");
});
<!--
test.html
-->
<button id="closeBtn">按钮</button>
<script>
closeBtn.onclick = function () {
var request = new XMLHttpRequest();
request.open("POST", "http://101.200.189.128:8081/data");
request.send();
};
</script>
简单模式禁止跨域基本代码
/**
* server.js
*/
// 修改语句为如下,其他不变
res.header("Access-Control-Allow-Origin","http://127.0.0.1");
预检请求
- 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
- 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的
XMLHttpRequest
请求,否则不会发出后续请求并报错。
以下是options预检请求消息示例
请求首部字段Access-Control-Request-Method表示该请求使用POST方法。
请求首部字段AcCesS-Control-Request-Headers表示该请求携带X-PINGOTHER首部字段。
预检模式下允许跨域案例代码
/**
* server.js
*/
// 修改语句为如下,其他不变,
res.header("Access-Control-Allow-Origin","*"); // 放开访客限制
预检模式下禁止跨域案例代码
<!-- test.html
-->
// 添加头部字段,这样请求会变成预检请求
request.setRequestHeader("Content-Type","application/json");
/**
* server.js
*/
// 修改语句为如下,其他不变,
// 限制访客地址只是禁止跨域的一种,也可以限制访问方法、限制访问头部字段
res.header("Access-Control-Allow-Origin","http://127.0.0.1");
想要发送Cookie和HTTP认证信息,必须在两方同时开启withCredentials属性。
// 服务器端
res.header("Access-Control-Allow-Credentials","true");
// 客户端
request.setRequestHeader("withCredentials","true");
此外,如果要发送Cookie,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。并且Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie
也无法读取服务器域名下的Cookie。
认证请求
CORS具有一个有趣的特性是,可以基于HTTP Cookies和HTTP认证信息发送身份凭证。一般而言,对于跨域XMLHtpRequest请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置ML上HtpRequest的某个特殊标志位。
xmlHlttpRequest.withCredentials = true;
将XMLHtpRequest的withCredentials标志设置为true,使得向服务器发送Cookies,服务器返回响应首部字段Access-Control-Allow-Credentials∶true。
如果服务器端的响应中未携带Access-Control-Alow-Credentials∶ true,浏览器将不会把响应内容返回给请求的发送者。
以下是请求消息示例
该请求携带了请求首部字段cookie的相关信息
以下是响应消息示例