JS笔记之跨域

阿毛0920
发布于 2022-6-28 23:46
浏览
0收藏

跨域原因

跨域是浏览器上才有的,因为浏览器的安全机制同源策略,所以诞生了一个这玩意。

但实际上你在浏览器向服务器请求获取已经成功了,服务器也响应了,给了你想要的东西,但是因为同源策略被浏览器给拦住了,不给你。

协议 域名 端口 这中间有一个不一样就会触发跨域问题

例如以下

  • 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

  1. 通过script标签获取来解决,但是要需要和后端规定好相应的回调函数,后端需要对JSONP单独给他写个响应格式
  2. 只能解决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

CORShttp1.1的一种跨域解决方案,全称 Cross-Origin Resource Sharing,跨域资源共享

大体思路是:浏览器跨域请求服务器资源,需要获得服务器的允许

因为有的请求只是获取一些资源,有的请求却会更改服务器的数据,这就需要辨别

所以针对不同的请求,CORS就给出了三个交互模式分别是

  • 简单请求
  • 需要预检的请求
  • 附带身份凭证的请求

三个模式从上到下,层层递进,请求的能做的事越来越多,也越来越严格

简单请求

当浏览器运行一段Ajax代码时,会先判断是哪一种请求,模式

简单请求的判断

请求必须同时满足以下条件

  1. 请求方法是以下一种
    • get
    • post
    • head
  2. 请求头仅包含安全字段,常见安全字段
    • Accept 表示客户端期望服务器返回的媒体格式
    • Accept-Language 表示客户端期望服务器返回内容的语言
    • Content-Language 表示服务响应给客户端body里面是什么语言
    • Content-Type 表示服务器向客户端发送的响应头,代表媒体类型和编码格式是对Accept和Accept-Charset的回应
  3. content-Type 仅限以下三个值
    • text/plain
    • multipart/from-data
    • application/x-www-from-urlencoded
  4. 请求中没有使用 ReadableStream对象
  5. 请求中的任意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

  1. 浏览器自动添加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 会告诉服务器那个域名在跨域

  2. 服务器响应头中应包含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();
}

需要预检的请求

当浏览器认为某个请求是非简单请求时,就会走下面流程

  1. 浏览器发送预检请求,询问服务器是否允许
  2. 服务器允许
  3. 浏览器是发送真实请求
  4. 服务器完成响应
不同浏览器方式也不同,谷歌是先发送预检,确认后再送请求内容,火狐好像是预检放在请求头和请求内容一起发过去

例如: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

浏览器与服务器交互流程如下

  1. 浏览器发送预检请求,询问服务器是否允许

    客户端发送的预检请求(浏览器自动发送)

    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 后续真正的请求头会改动的内容
  2. 服务器允许

    服务器收到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秒内就不用再预检了。
  3. 浏览器发送真正的请求

    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
    ...
    
    请求体内容
    
  4. 服务器响应真正的请求

    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已经有大佬写好写好了,直接拿来用就行了…

分类
标签
已于2022-7-1 15:09:31修改
3
收藏
回复
举报
1条回复
按时间正序
/
按时间倒序
lxj29
lxj29

写的不错哦

回复
2022-7-20 18:02:58
回复
    相关推荐