字节一面:你能讲一下跨域吗

前言

最近博主在字节面试中遇到这样一个面试题,这个问题也是前端面试的高频问题,作为一名前端开发工程师,我们日常开发中与后端联调时一定会遇到跨域的问题,只有处理好了跨域才能够与后端交互完成需求,所以深入学习跨域是很有必要的,博主在这给大家细细道来。


🚀 作者简介:程序员小豪,全栈工程师,热爱编程,曾就职于蔚来、腾讯 ,现就职于某互联网大厂,技术栈:Vue、React、Python、Java

🎈 本文收录于小豪的前端系列专栏,后续还会更新前端入门以及前端面试 的一些相关文章,手把手带你从零学习前端到面试找工作,并如果有想进入前端领域工作的同学,这个前端专栏会对你有所帮助,欢迎关注起来呀

🌼 本人也会持续的去关注AIGC以及人工智能领域的一些动向并总结到博客中,大家感兴趣的可以关注一下我的人工智能专栏

🌊 云原生的入门学习系列,大家有兴趣的可以看一看

本文目录

什么是跨域?

首先一个url是由:协议、域名、端口 三部分组成。(一般端口默认80)

如:https://blog.xxx.cn:80

当一个请求url的协议域名端口三者之间的任意一个与当前页面url不同即为跨域

例如:

当前页面url 被请求页面url 是否跨域 原因
http://www.xxx.com/ http://www.xxx.com/index.html 同源(协议、域名、端口号相同)
http://www.xxx.com/ https://www.xxx.com/index.html 跨域 协议不同(http/https)
http://www.xxx.com/ http://www.baidu.com/ 跨域 主域名不同(test/baidu)
http://www.xxx.com/ http://xxx.com/ 跨域 子域名不同(www/xxx)
http://www.xxx.com:8080/ http://www.xxx.com:7001/ 跨域 端口号不同(8080/7001)

跨域产生原因?

出于浏览器的同源策略限制。同源策略(Same Orgin Policy)是一种约定,它是浏览器核心也最基本的安全功能,它会阻止一个域的js脚本和另外一个域的内容进行交互,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。

所谓同源(即在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)。

非同源会出现的限制

  • 无法读取非同源网页的cookie、localstorage等
  • 无法接触非同源网页的DOM和js对象
  • 无法向非同源地址发送Ajax请求

如何跨域?

CORS请求

浏览器将cors请求分为两类:简单请求非简单请求

只要同时满足以下两大条件,就属于简单请求

  1. 请求方法是以下三种方法之一:
    • HEAD
    • GET
    • POST
  2. HTTP的头信息不超出以下几种字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

简单请求

基本流程

简单请求,浏览器都是直接发出cors请求,其实就是在头信息中,加一个Origin字段

http 复制代码
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

Origin字段就是用来说明本次请求来自哪个源,服务器根据这个值,决定是否同意本次请求

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的http回应,这个回应就不包含Access-Control-Allow-Origin,那么就说明本次请求出错了,这个错误无法通过状态码识别,因为http回应的状态码可能是200。

如果Origin指定域名在许可范围内,服务器返回的响应,就会多出几个头信息字段

http 复制代码
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的请求头信息中,有三个是和cors请求有关系的,都以Access-Control-开头

  1. Access-Control-Allow-Origin

    这个字段是必须的,它的值要么是请求时Origin字段的值,要么是*,表示接受任意域名的请求

  2. Access-Control-Allow-Credentials

    这个字段的值类型是bool类型的,表示是否允许发送cookie,默认情况下,cookie不包含在cors请求中,设为true

  3. Access-Control-Expose-Header

    该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

withCredentials 属性

上面说到CORS请求默认不发送cookie和http认证信息。如果要把cookie发到服务器,一方面,要服务器同意,指定Access-Control-Allow-Credentials字段

http 复制代码
Access-Control-Allow-Credentials: true

还要浏览器同意,开发者必须在ajax请求中打开withCredentials属性:

js 复制代码
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

非简单请求

预检请求

非简单请求是对服务器有特殊请求的请求,比如请求方法为PUTDELETE,或者Content-Type字段类型是application/json

非简单请求的cors请求,会在正式通信前,增加一次HTTP查询请求,称为"预检请求"

这个预检请求就是浏览器向服务器询问,当前域名是否在服务器的白名单中,以及可以使用哪些请求方法和请求头字段,只有得到肯定回复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错

浏览器发送请求的代码

js 复制代码
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

上面请求的方法是PUT,并且还发送了一个自定义头部字段X-Custom-Header

浏览器发现这是一个非简单请求,就发出一个"预检请求",这个请求的头部信息如下

http 复制代码
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

还有两个特殊字段:

  1. Access-Control-Request-Method

    这个字段是必须的,用来列出浏览器CORS请求会用的http方法

  2. Access-Control-Request-Headers

    该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

预检请求的回应

服务器收到"预检"请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

http 复制代码
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

(1)Access-Control-Allow-Methods

该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

(2)Access-Control-Allow-Headers

如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。

(3)Access-Control-Allow-Credentials

该字段与简单请求时的含义相同。

(4)Access-Control-Max-Age

该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

浏览器的正常请求和回应

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

下面是"预检"请求之后,浏览器的正常CORS请求。

http 复制代码
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面头信息的Origin字段是浏览器自动添加的。

下面是服务器正常的回应

http 复制代码
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。

与JSONP的比较

CORS与JSONP的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

nginx反向代理解决跨域(前端常用)

前端通过nginx代理将前端的请求转发到真正的后端域名上,就避免跨域了

nginx配置

nginx 复制代码
server{
    # 监听9099端口
    listen 9099;
    # 域名是localhost
    server_name localhost;
    #凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871 
    location ^~ /api {
        proxy_pass http://localhost:9871;
    }    
}

JSONP

在HTML标签里,一些标签比如script、img这样的获取资源的标签是没有跨域限制的,所以可以利用他们发去跨域请求,这就叫jsonp

后端

js 复制代码
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async jsonp (ctx) {
    // 前端传过来的参数
    const query = ctx.request.query
    // 设置一个cookies
    ctx.cookies.set('tokenId', '1')
    // query.cb是前后端约定的方法名字,其实就是后端返回一个直接执行的方法给前端,由于前端是用script标签发起的请求,所以返回了这个方法后相当于立马执行,并且把要返回的数据放在方法的参数里。
    ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})`
  }
}
module.exports = CrossDomain

前端

js 复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <script type='text/javascript'>
      // 后端返回直接执行的方法,相当于执行这个方法,由于后端把返回的数据放在方法的参数里,所以这里能拿到res。
      window.jsonpCb = function (res) {
        console.log(res)
      }
    </script>
    <script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script>
  </body>
</html>

其实就是在发送请求的链接带上一个cb的参数,这个cb就是script里的一个函数名称,然后后端将这个函数名称取出来,将要返回的数据放入这个函数的参数里,后端返回这个函数给前端,前端直接执行。

postMessage

window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+FireFoxChromeOpera等浏览器都已经支持window.postMessage方法。

调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 *

需要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。

js 复制代码
// 发送消息端
window.parent.postMessage('message', 'http://test.com')
// 接收消息端
var mc = new MessageChannel()
mc.addEventListener('message', event => {
var origin = event.origin || event.originalEvent.origin
    if (origin === 'http://test.com') {
    	console.log('验证通过')
    }
})

总结

各位看官老爷们好,小豪已经建立了技术交流群,如果你很感兴趣,可以私信我加入我的社群。

📝社群中不定时会有很多活动,例如学习资料分享、大厂面经分享、技术讨论、行业大佬创业杂谈等等。

📝本人目前是在互联网大厂正式工作,也有过多个大厂的工作经历,加入社群也会有简历修改辅导,模拟面试,手把手项目实战教学,大厂工作内推机会以及大厂面试题解析分享等福利。

📝社群方向很多,相关领域有Web全栈(前后端)、人工智能、AIGC、自媒体变现、前沿科技文章分享、论文精读等等。

📝不管你是多新手的小白,都欢迎你加入社群中讨论、聊天、分享,加速助力你成为下一个技术大佬!也随时欢迎您跟我沟通,一起交流,一起成长。变现、进步、技术、资料、项目、你想要的这里都会有

📝网络的风口只会越来越大,风浪越大,鱼越贵!欢迎您加入社群~一个人可以或许可以走的很快,但一群人将走的更远!

📝想都是问题,做都是答案!行动起来吧!欢迎评论区or后台与我沟通交流,也欢迎您扫描下方二维码直接加入到我的交流社群!(微信:adcoderhao)

相关推荐
magic 2459 分钟前
移动端WEB开发之响应式布局
前端·css·html·html5
胡桃夹夹子11 分钟前
uniapp自身bug | uniapp+vue3打包后 index.html无法直接运行
javascript·uni-app·bug
fridayCodeFly12 分钟前
:class=“{ ‘addCheckstyle‘: hasError }“这是什么意思
前端·javascript·vue.js
知识分享小能手15 分钟前
CSS3学习教程,从入门到精通,CSS3 布局语法知识点及案例代码(15)
前端·css·学习·html·css3·html5·java后端开发
木木黄木木16 分钟前
使用CSS3实现炫酷的3D翻转卡片效果
前端·3d·css3
Sperains29 分钟前
响应式数组操作在Vue3和React中的差异
前端
狼头长啸李树身29 分钟前
今日春分节气,是旅游旺季的起点
网络·旅游·媒体
阿黄学技术35 分钟前
Spring框架核心注解(Spring,SpringMVC,SpringBoot)
前端·spring boot·spring
小璞35 分钟前
1. Webpack 核心概念
前端·webpack
lee57642 分钟前
用Promise实现ajax的自动重试
前端·javascript·ajax