JS笔记之跨域
跨域原因
跨域是浏览器上才有的,因为浏览器的安全机制同源策略,所以诞生了一个这玩意。
但实际上你在浏览器向服务器请求获取已经成功了,服务器也响应了,给了你想要的东西,但是因为同源策略
被浏览器给拦住了,不给你。
协议 域名 端口
这中间有一个不一样就会触发跨域问题
例如以下
-
http://www.zhouyumao.top:8080
->http://www.zhouyumao.top:80
原因端口不一样 -
http://www.zhouyumao.top
->http://www.blabla.com
原因域名不一样 -
http://www.zhouyumao.top
->https://www.zhouyumao.top
原因协议不一样 -
另外一种情况,同一个域名下的子域名不一样也会触发跨域如,
http://zh.zhouyumao.top
->http://www.zhouyumao.top
原因也是域名不一样
解决跨域
JSONP
- 通过script标签获取来解决,但是要需要和后端规定好相应的回调函数,后端需要对JSONP单独给他写个响应格式
- 只能解决
GET
请求问题,因为script标签只能发送GET
请求
所以JSONP并不是一种好的解决方案,这是很老的方案了
JSONP 客户端代码
function jsonp(url){
var dom = document.createElement("script");
dom.src = url;
document.body.appendChild(dom);
dom.onload = function (){
dom.remove();
}
}
function cb(data){
console.log(data);
}
服务端部分代码
const json = JSONP.stringify(result); //result 是要发送个客户端的资源
cosnt script = `cb(${json})`;
res.header('content-type','application/javascript').send(script);//这里服务器是用express搭的
CORS
CORS
是http1.1
的一种跨域解决方案,全称 Cross-Origin Resource Sharing,跨域资源共享
大体思路是:浏览器跨域请求服务器资源,需要获得服务器的允许
因为有的请求只是获取一些资源,有的请求却会更改服务器的数据,这就需要辨别
所以针对不同的请求,CORS就给出了三个交互模式分别是
- 简单请求
- 需要预检的请求
- 附带身份凭证的请求
三个模式从上到下,层层递进,请求的能做的事越来越多,也越来越严格
简单请求
当浏览器运行一段Ajax代码时,会先判断是哪一种请求,模式
简单请求的判断
请求必须同时满足以下条件
- 请求方法是以下一种
- get
- post
- head
- 请求头仅包含安全字段,常见安全字段
- Accept 表示客户端期望服务器返回的媒体格式
- Accept-Language 表示客户端期望服务器返回内容的语言
- Content-Language 表示服务响应给客户端body里面是什么语言
- Content-Type 表示服务器向客户端发送的响应头,代表媒体类型和编码格式是对Accept和Accept-Charset的回应
- content-Type 仅限以下三个值
- text/plain
- multipart/from-data
- application/x-www-from-urlencoded
- 请求中没有使用 ReadableStream对象
- 请求中的任意XMLHttpRequest对象均没有注册任何事件,可以使用XMLHttpRequest.upload()访问
例子
//简单请求
fetch('http://www.zhouyumao.top');
//非简单请求,不满足第一种
fetch('http://www.zhouyumao.top',{
method:"put"
});
//简单请求
fetch("http://www.zhouyumao.top",{
method:"post"
})
//content-type 不符合
fetch("http://www.zhouyumao.top",{
method:"get",
headers:{
"content-type":"image/gif"
}
})
//加了额外的请求头
fetch("http://www.zhouyumao.top",{
method:"get",
headers:{
as:"asd"
}
})
简单请求规范
当进行Ajax请求时,被浏览器判定跨域会发生以下情况
例如在http://www.zhouyumao.top/index.html
中发生了跨域请求 http://zh.zhouyumao.top/api/user
-
浏览器自动添加
Origin
字段请求头部分字段
GET /api/user HTTP1.1 Host:zh.zhouyumao.top Connection: keep-alive ... Referer:http://www.zhouyumao.top/index.html Origin:http://www.zhouyumao.top
最后一行
Origin
会告诉服务器那个域名在跨域 -
服务器响应头中应包含Access-Control-Allow-Origin
当服务器收到请求后,如果允许跨域,需要要在响应头中添加Access-Control-Allow-Origin字段
字段值有两种
- “*” 表示允许任何网站跨域,(并不推荐使用,因为如果后面请求附带身份认证的话就会不行,报错)
- 具体的源,表示值与请求头
Origin
的值一样 例如Access-Control-Allow-Origin : http://www.zhouyumao.top
一般来说,服务器可以设置可跨域白名单,通过确认白名单来告诉服务器是否可以跨域
服务器响应
HTTP/1.1 200 OK
...
Access-Control-Allow-Origin:http://www.zhouyumao.top
...
服务端支持简单请求跨域(nodejs环境使用express搭建服务器插入一个中间件)
const allowOrigin =["www.zhouyumao.top",'zh.zhouyumao.top','cc.zhouyumao.top'];//可跨域白名单
module.exports= function(req,res,next){
if("origin" in req.headers && allwOrigin.includes(req.headers.origin)){
res.header("access-control-allow-origin",req.headers.origin);//给响应头添加Access-Control-Allow-Origin
}
next();
}
需要预检的请求
当浏览器认为某个请求是非简单请求时,就会走下面流程
- 浏览器发送预检请求,询问服务器是否允许
- 服务器允许
- 浏览器是发送真实请求
- 服务器完成响应
不同浏览器方式也不同,谷歌是先发送预检,确认后再送请求内容,火狐好像是预检放在请求头和请求内容一起发过去
例如:http:www.zhouyumao.top/index.html
中有Ajax请求跨域了
fetch("http://cc.zhouyumao.top/api/user",{
method:"post",
headers:{
"content-type":"application/json",
i:1
},
body:JSON.stringify({userId:"123",pw:"12321312"})
})
当发送该请求时,浏览器就会发现这不是简单请求,headers
中添加了一个不属于安全字段的i
字段、Content-Type
字段值也不是限定的三个值之一,并且也增加了一个请求体body
浏览器与服务器交互流程如下
-
浏览器发送预检请求,询问服务器是否允许
客户端发送的预检请求(浏览器自动发送)
OPTIONS /api/user HTTP1.1 Host:cc.zhouyumao.top Origin:http:cc.zhouyumao.top Access-Control-Request-Method:POST Access-Control-Request-Headers:i,content-type ...
这是一个预检请求,询问服务器是否允许后续请求操作
预检请求有以下特征
- 请求方法为
OPTIONS
- 没有请求体
- 请求头包含
Origin
和简单请求一样Access-Control-Request-Method
后续真正的请求方法Access-Control-Request-Headers
后续真正的请求头会改动的内容
- 请求方法为
-
服务器允许
服务器收到
OPTIONS
预检请求后,就会检查是否允许,如果允许就会响应包含以下格式的消息HTTP/1.1 200 OK ... Access-Control-Allow-Methods:POST Access-Control-Allow-Headers:content-type,i Access-Control-Allow-Origin:http:www.zhouyumao.top Access-Control-Max-Age:36000 ...
服务器对预检请求,响应不带响应体,只是发送个响应头给浏览器
Access-Control-Allow-Methods
允许的请求方法Access-Control-Allow-Headers
允许改动的请求头Access-Control-Allow-Origin
和简单请求一样,允许的跨域源- Access-Control-Max-Age 告浏览器,下次同样的源,方法,请求头在36000秒内就不用再预检了。
-
浏览器发送真正的请求
POST /api/user HTTP/1.1 Host:http://cc.zhouyumao.top Connection:keep-alive ... Referer:http://www.zhouyumao.top/index.html Origin:http://www.zhouyumao.top ... 请求体内容
-
服务器响应真正的请求
HTTP/1.1 200 ok ... Access-Control-Allow-Origin:http://www.zhouyumao.top ... 响应内容
交互到这完成
服务端部分代码
const allowOrigin =["www.zhouyumao.top",'zh.zhouyumao.top','cc.zhouyumao.top'];//可跨域白名单 module.exports= function(req,res,next){ if(req.method === "OPTION"){ res.header("Access-Control-Allow-Methods",req.headers["access-control-request-method"]); res.header("Access-Control-Allow-Headers",req.headers["access-control-request-headers"]); res.header("Access-Control-Max-Age","3600"); } if('origin' in req.headers && allowOrigin.includes(req.headers.origin)){ res.header("access-control-allow-origin",req.headers.origin); } next(); }
附带身份请求
对于跨域请求,浏览器是默认不带Cookie的,如果没带Cookie一些验证就不能继续
不过也可以自己配
fetch(url,{
credentials:"include"
})
这样Ajax请求就会附带Cookie字段,简单请求,预检都会带
服务器端响应时,得在响应头添加,Access-Control-Allow-Credentials:true
如果没有,浏览器即视为跨域拒接
上面说过服务器响应Access-Control-Allow-Origin
不能设为“*”,就是因为当需要附带身份认证时,必须有具体的Origin
源,
服务器端
const allowOrigin =["www.zhouyumao.top",'zh.zhouyumao.top','cc.zhouyumao.top'];//可跨域白名单
module.exports= (req,res,next)=>{
if(req.method === "OPTION"){
res.header("Access-Control-Allow-Methods",req.headers["access-control-request-method"]);
res.header("Access-Control-Allow-Headers",req.headers["access-control-request-headers"]);
res.header("Access-Control-Max-Age","3600");
}
if('origin' in req.headers && allowOrigin.includes(req.headers.origin)){
res.header("access-control-allow-origin",req.headers.origin);
res.header('Access-Control-Allow-Creadential',true);
}
next();
}
}
另外在跨域访问时,js只能读取到以下七种
Cache-Control
Content-Language
Content-Length
Content-Type
Expires
Last-Modified
Pragma
需要在服务器端配置Access-Control-Expose-Headers
例如,Access-Control-Expose-Headers:i,a,b'
结尾
其实CORS已经有大佬写好写好了,直接拿来用就行了…
写的不错哦