【http】2、http request header Origin 属性、跨域 CORS、同源、nginx 反向代理、预检请求

http header Origin、跨域、同源

一、Origin 含义

origin 是来源,原始的意思

The Origin request header indicates the origin (scheme, hostname, and port) that caused the request. For example, if a user agent needs to request resources included in a page, or fetched by scripts that it executes, then the origin of the page may be included in the request.

http request header 的 Origin 字段,的内容是 scheme,hostname,port 字段。代表请求的来源。

例如,如果一个 client,需要请求一个网页资源或脚本,request header 会带有自己的信息。

格式:

Origin: null
Origin: <scheme>://<hostname>
Origin: <scheme>://<hostname>:<port>

格式:

  • null:origin 是隐私或者不透明的
  • scheme:协议,通常是 http 或 https
  • hostname:域名或 ip
  • port:(可选的)端口号,数字

描述:

Origin header 和 Referer header 很像,但不透露路径,且可能为空。其表示了来源信息,除非是隐私或不必要的。

一般而言,用户代理将原始请求报头添加到:

  • cross-origin requests(跨域请求)。
  • same-origin request(同源请求),除了 GET、HEAD 的方法(即 POST、OPTIONS、PUT、PATCH、DELETE)。

以下情况,值可能为 null:

  • 不是 http, https, ftp, ws, wss, or gopher (including blob, file and data) 的 scheme。
  • 跨源图像和媒体数据,包括中的、和元素。

示例:

Origin: https://developer.mozilla.org
Origin: http://developer.mozilla.org:80

参考:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin

二、跨源资源共享:Cross-Origin Resource Sharing CORS

参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS

2.1 跨域的定义

一个协议的 url,由 协议、域名、端口三部分组成。当一个请求 url 的协议、域名、端口三者的任意一个,与当前页面 url 不同即为跨域。

例如:

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

跨域的原因:

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

非同源会出现的限制:

无法读取非同源网页的 cookie、localstorage 等

无法接触非同源网页的 DOM 和 js 对象

无法向非同源地址发送 ajax 请求

概念:

跨源资源共享(CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他 Origin(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。

跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的 preflight(“预检”)请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。

跨源 HTTP 请求的一个例子:运行在 https://domain-a.com 的 JavaScript 代码使用 XMLHttpRequest 来发起一个到 https://domain-b.com/data.json 的请求。

出于安全性,浏览器限制脚本内发起的跨源 HTTP 请求。例如,XMLHttpRequestFetch API 遵循同源策略。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。

如下图,首先从 domain-a.com 加载了 index.html,则从 index.html(origin=domain-a.com) 加载layout.css(origin=domain-a.com) 和 image.png(origin=domain-a.com) ,则都是同源的。

但是,如果从 index.html(origin=domain-b.com) 加载 layout.css(origin=domain-a.com) 和 image.png(origin=domain-a.com) ,则是跨域的(即 origin 不同)。

CORS 机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequestFetch)使用 CORS,以降低跨源 HTTP 请求所带来的风险。

2.2 功能概述

跨源资源共享标准新增了一组 HTTP 标头字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(例如 CookieHTTP 认证相关数据)。

CORS 请求失败会产生错误,但是为了安全,在 JavaScript 代码层面无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。

2.3 场景示例

2.3.1 简单请求

比如说,假如站点 https://foo.example 的网页应用想要访问 https://bar.other 的资源。foo.example 的网页中可能包含类似于下面的 JavaScript 代码:

const xhr = new XMLHttpRequest();
const url = "https://bar.other/resources/public-data/";

xhr.open("GET", url);
xhr.onreadystatechange = someHandler;
xhr.send();

以下是浏览器发送给服务器的请求报文:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example

其中,Origin 字段表明该请求来源于 http://foo.example

让我们来看看服务器如何响应:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[…XML Data…]

本例中,服务端返回的 Access-Control-Allow-Origin 标头的 Access-Control-Allow-Origin: * 值表明,该资源可以被任意外源访问。

使用 OriginAccess-Control-Allow-Origin 就能完成最简单的访问控制。如果 https://bar.other 的资源持有者想限制他的资源只能通过 https://foo.example 来访问(也就是说,非 https://foo.example 域无法通过跨源访问访问到该资源),他可以这样做:

Access-Control-Allow-Origin: https://foo.example

2.3.2 Preflighted requests:预检请求

简单请求不同,“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

如下是一个需要执行预检请求的 HTTP 请求:

const fetchPromise = fetch("https://bar.other/doc", {
  method: "POST",
  mode: "cors",
  headers: {
    "Content-Type": "text/xml",
    "X-PINGOTHER": "pingpong",
  },
  body: "<person><name>Arun</name></person>",
});

fetchPromise.then((response) => {
  console.log(response.status);
});

上面的代码使用 POST 请求发送一个 XML 请求体,该请求包含了一个非标准的 HTTP X-PINGOTHER 请求标头。这样的请求标头并不是 HTTP/1.1 的一部分,但通常对于 web 应用很有用处。另外,该请求的 Content-Typeapplication/xml,且使用了自定义的请求标头,所以该请求需要首先发起“预检请求”。

下面是服务端和客户端完整的信息交互。首次交互是预检请求/响应

OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive

从上面的报文中,我们看到,第 1 - 10 行使用 OPTIONS 方法发送了预检请求,浏览器根据上面的 JavaScript 代码片断所使用的请求参数来决定是否需要发送,这样服务器就可以回应是否可以接受用实际的请求参数来发送请求。OPTIONS 是 HTTP/1.1 协议中定义的方法,用于从服务器获取更多信息,是安全的方法。该方法不会对服务器资源产生影响。注意 OPTIONS 预检请求中同时携带了下面两个标头字段:

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

标头字段 Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法。标头字段 Access-Control-Request-Headers 告知服务器,实际请求将携带两个自定义请求标头字段:X-PINGOTHERContent-Type。服务器据此决定,该实际请求是否被允许。

第 12 - 21 行为预检请求的响应,表明服务器将接受后续的实际请求方法(POST)和请求头(X-PINGOTHER)。重点看第 15 - 18 行:

Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

服务器的响应携带了 Access-Control-Allow-Origin: ``https://foo.example,从而限制请求的源域。同时,携带的 Access-Control-Allow-Methods 表明服务器允许客户端使用 POSTGET 方法发起请求(与 Allow 响应标头类似,但该标头具有严格的访问控制)。

标头字段 Access-Control-Allow-Headers 表明服务器允许请求中携带字段 X-PINGOTHERContent-Type。与 Access-Control-Allow-Methods 一样,Access-Control-Allow-Headers 的值为逗号分割的列表。

最后,标头字段 Access-Control-Max-Age 给定了该预检请求可供缓存的时间长短,单位为秒,默认值是 5 秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。以上例子中,该响应的有效时间为 86400 秒,也就是 24 小时。请注意,浏览器自身维护了一个最大有效时间,如果该标头字段的值超过了最大有效时间,将不会生效。

预检请求完成之后,发送实际请求:

POST /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache

<person><name>Arun</name></person>

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some XML payload]

2.4 header

2.4.1 http request header

2.4.1.1 Origin

Origin 标头字段表明预检请求或实际跨源请求的源站。

Origin: <origin>
2.4.1.2 Access-Control-Request-Method

Access-Control-Request-Method 标头字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。

Access-Control-Request-Method: <method>
2.4.1.3 Access-Control-Request-Headers

Access-Control-Request-Headers 标头字段用于预检请求。其作用是,将实际请求所携带的标头字段(通过 setRequestHeader() 等设置的)告诉服务器。这个浏览器端标头将由互补的服务器端标头 Access-Control-Allow-Headers 回答。

Access-Control-Request-Headers: <field-name>[, <field-name>]*

2.4.2 http response header

2.4.2.1 Access-Control-Allow-Origin

响应标头中可以携带一个 Access-Control-Allow-Origin 字段,其语法如下:

Access-Control-Allow-Origin: <origin> | *

Access-Control-Allow-Origin 参数指定了单一的源,告诉浏览器允许该源访问资源。或者,对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符“*”,表示允许来自任意源的请求。

例如,为了允许来自 https://mozilla.org 的代码访问资源,你可以指定:

Access-Control-Allow-Origin: https://mozilla.org
Vary: Origin

如果服务端指定了具体的单个源(作为允许列表的一部分,可能会根据请求的来源而动态改变)而非通配符“*”,那么响应标头中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的 Origin 返回不同的内容。

2.4.2.2 Access-Control-Expose-Headers

在跨源访问时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。

Access-Control-Expose-Headers 头将指定标头放入允许列表中,供浏览器的 JavaScript 代码(如 getResponseHeader())获取。

Access-Control-Expose-Headers: <header-name>[, <header-name>]*

例如:

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

这样浏览器就能够通过 getResponseHeader 访问 X-My-Custom-HeaderX-Another-Custom-Header 响应头了。

2.4.2.3 Access-Control-Max-Age

Access-Control-Max-Age 头指定了 preflight 请求的结果能够被缓存多久,请参考本文在前面提到的 preflight 例子。

Access-Control-Max-Age: <delta-seconds>

delta-seconds 参数表示 preflight 预检请求的结果在多少秒内有效。

2.4.2.4 Access-Control-Allow-Credentials

Access-Control-Allow-Credentials 头指定了当浏览器的 credentials 设置为 true 时是否允许浏览器读取 response 的内容。当用在对 preflight 预检测请求的响应中时,它指定了实际的请求是否可以使用 credentials。请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。

2.5 解决方案

2.5.1 nginx 反向代理

正向代理:a -> c -> b

因为 a 希望访问 b,但无法直接访问。所以设置 client c 为 client a 的正向代理,即 a 的请求,都由 c 发出。

反向代理:a -> c <- b

因为 a 希望访问 c, 但 c 的内容其实来自 b,server c 是 server b 的反向代理。

比如浏览器输入 http://192.168.2.99/website 访问主页,会命中下文的 nginx 配置文件:首先 listen 了 80 端口,其次请求的路径命中了 location /,最终返回 $root/website.html(因为 tryfiles 是 $uri.html),其中 $root 即为 /opt/frontend-project/latest/,即最终访问的是 /opt/frontend-project/latest/website.html。

server {
    listen 80;

    root /opt/frontend-project/latest/;
    index index.html index.htm;

    # Make site accessible from http://localhost/
    server_name _;

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri.html /index.html;
        # Uncomment to enable naxsi on this location
        # include /etc/nginx/naxsi.rules
    }
}

浏览器拿到 nginx 静态资源服务器返回的 index.html 之后,即会继续加载 html 里嵌套的 css 和 js。js 访问一个地址时,可能跨域(主要是端口号)。因为 html/css/js 的源是 80 端口(即 nginx 静态资源服务器)。但期望加载的数据是 34567 端口(即后端 http 接口 server)。因为 port 不同,所以不同源,即跨域。浏览器因为安全规范会拒绝此请求。

为了解决跨域问题,有两种方式:

  • 方式一:都用后端 http 接口 server做源,即用后端 http 接口 server,来代理前端静态资源(html/css/js),这样静态资源和接口都是同一个源(即后端的)。但这目前为了开发效率,都采用了微服务的前后端项目分离架构,为了分离部署,不希望后端还耦合前端的部署。
  • 方式二:都用 nginx server 做源。即 nginx 提供静态资源,还提供后端 http 接口 server 的反向代理。

方式二 nginx 配置的反向代理部分,如下:

server {
    listen 80;

    root /opt/frontend-project/latest/;
    index index.html index.htm;

    # Make site accessible from http://localhost/
    server_name _;

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri.html /index.html;
        # Uncomment to enable naxsi on this location
        # include /etc/nginx/naxsi.rules
    }

    location /proxy/backend-project1/ {
        proxy_pass http://127.0.0.1:34567/;
    }

    location /proxy/backend-project2/ {
        proxy_pass http://127.0.0.1:30000/;
    }
    
    location /ws {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Proxy "";
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://127.0.0.1:34567;
    }
}

2.5.2 预检请求 + 后端添加 response header 解决跨域

浏览器先预检请求询问b, b 通过 response header 允许a访问。b 的 response header 示例如下:

header('Access-Control-Allow-Origin:*');//允许所有来源访问
header('Access-Control-Allow-Method:POST,GET');//允许访问的方式

具体操作方式:

  • 前端:所有浏览器,均支持跨域,由浏览器自动完成,不需要程序员参与。
  • 后端:后端判断请求,若请求希望跨域,则添加 response header,一般通过路由中间件完成。

优势:在后端可以自定义跨域规则,更灵活

缺点:会产生额外的预检请求

  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呆呆的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值