现代网页比以往任何时候都使用更多的外部脚本和资产。默认情况下,JavaScript 遵循同源策略,只能调用与运行脚本在同一域中的 URL。那么,我们怎样才能让我们的 JavaScript 支持的页面使用外部脚本呢?
CORS 就是答案。
跨源资源共享 (CORS) 是一种允许网页访问在不同受限域上运行的API或资产的方式的机制。
什么是 CORS?
跨源资源共享 (CORS) 是一种浏览器机制,允许网页使用来自其他页面或域的资产和数据。
大多数站点需要使用资源和图像来运行它们的脚本。这些嵌入式资产存在安全风险,因为这些资产可能包含病毒或允许服务器访问黑客。
安全策略减轻了资产使用的安全风险。该政策规定了请求站点可以根据来源或内容加载哪些资产,并规定了提供给请求站点的访问量。每个策略都必须有足够的限制来保护 Web 服务器,但又不至于损害功能。
同源是最安全的策略类型,可防止访问任何外部服务器。站点的所有资产必须来自同一来源。大多数时候,同源是一个不错的选择,因为大多数脚本只能使用本地资源。但是,有时我们会希望允许访问外部资产,例如视频、直播或图片。
什么是起源? Origin指的是3部分:协议,主机,端口号。Protocol指的是应用层协议,通常是HTTP。主机是所有页面所属的主要站点域,例如 Educative.io。最后,端口号是请求的通信端点,默认为80端口。
许多站点使用一种称为跨源资源共享(CORS)的跨源策略形式,它定义了网页和主机服务器交互的方式,并确定服务器允许访问该网页是否安全。
CORS 是安全性和功能性之间的中间地带策略,因为服务器可以批准某些外部请求而无需批准所有请求的不安全性。
CORS 实例
CORS 最普遍的例子是非本地网站上的广告。
例如,假设您在观看 YouTube 视频时看到了 Android 广告。YouTube 的服务器为其基本资源预留,无法在本地存储所有可能的广告。
相反,所有广告都存储在广告公司的服务器上。广告公司已允许访问 YouTube 以允许 YouTube 网页播放存储的 Android 广告视频。
该系统的好处是 YouTube 可以使用来自另一台服务器的内容,而无需使用本地存储。此外,它还允许广告公司快速推出新广告,因为他们只需要更新从他们的服务器传递到 YouTube 的广告。
CORS 可以请求哪些资产?
站点使用 CORS 请求加载:
- 获取请求或 HTTP 请求,如
XMLHTTPRequests
- Web 字体和 TrueType 字体仅适用于跨站点加载
- Web GL 纹理
- 图片和视频
- CSS 形状
您可以使用 CORS 将这些类型的资产自由地嵌入到您的站点中,并避免创建本地副本。
CORS 是如何工作的?
CORS 将新的 HTTP 标头添加到标准标头列表中。新的 CORS 标头允许本地服务器保留允许的来源列表。
来自这些来源的任何请求都会得到批准,并且允许他们使用受限资产。添加到可接受来源列表的标头是Access-Control-Allow-Origin
.
有许多不同类型的响应标头可以实现不同级别的访问。 以下是CORS HTTP 标头的更多示例:
Access-Control-Allow-Credentials
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Expose-Headers
Access-Control-Max-Age
Access-Control-Request-Headers
Access-Control-Request-Method
Origin
当 Web 浏览器想要访问站点时,它会向站点服务器发送 CORSGET
请求。如果获得批准,GET
请求将允许浏览器查看页面,但仅此而已。
大多数服务器允许GET
来自任何来源的请求,但会阻止其他类型的请求。
服务器将发回通配符值 ,*
这意味着对所请求数据的访问不受限制,或者服务器将检查允许的来源列表。
如果请求者的来源在列表中,则允许该网页查看该网页,并且服务器回显允许来源的名称。
如果不是,服务器将返回一条拒绝消息,说明是否不允许源进行所有访问或是否不允许进行特定操作。
CORS 请求的类型
上面的请求GET
是最简单的只允许查看的请求形式。有不同类型的请求允许更复杂的行为,例如数据操作或删除的跨域请求。
存在这些不同的请求是因为我们可能希望根据来源授予不同级别的访问权限。也许我们希望所有GET
请求都得到批准,但只有我们合作的广告公司可以编辑资产。
请求类型的分离使我们能够决定源的确切许可级别,并确保每个源只能执行对其功能至关重要的请求。
大多数请求分为两大类:
- 简单请求:这些请求不会触发预检并仅使用“安全列表”CORS 标头。
- 预检请求:这些请求发送“预检”消息,概述请求者在原始请求之前想要做什么。请求的服务器检查此预检消息以确保请求是安全的。
简单请求
简单请求不需要预检并使用以下三种方法之一:GET
、POST
和HEAD
。这些请求来自 CORS 发明之前,因此可以跳到 CORS 预检。
GET
:
该GET
请求要求查看来自特定 URL 的共享数据文件的表示。它还可以用于触发文件下载。
一个例子是访问网络上的任何站点。作为外部用户,我们只能看到网站的内容,不能更改文本或视觉元素。
代码语言:javascript复制GET /index.html
HEAD
:
该HEAD
请求预览将与请求一起发送的标头GET
。它用于在不访问特定 URL 的情况下对特定 URL 中存在的内容进行采样。
例如,您可以HEAD
下载 URL 来接收其Content-Length
标头。这会让您在同意下载之前知道下载的文件大小。
HEAD /index.html
POST
:
该POST
请求要求将数据传输到请求的服务器,这可能会导致服务器发生变化。如果一个POST
请求被多次触发,它可能会有意想不到的行为。
这方面的一个例子是向论坛线程添加评论。
浏览器向服务器发送添加您输入的评论的请求。一旦被接受,论坛服务器就会获取新收到的数据(评论)并将其存储起来以供其他人查看。
代码语言:javascript复制POST /test HTTP/1.1
Host: foo.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
field1=value1&field2=value2
预检请求
一些方法会生成一个额外的预检请求,该请求会在原始请求之前发送。OPTIONS
Preflight 请求是使用可影响用户数据或在服务器中进行重大更改的功能的方法自动生成的。
该OPTIONS
方法用于收集有关如何允许请求者与服务器交互的更多信息。它返回请求者被批准的方法选项。
OPTIONS
是一种安全的方法,这意味着它不能更改访问的任何内容。out,因为如果您使用预检方法,它将在幕后发送。
您不需要手动调用该OPTIONS
方法。当您尝试请求标记为“待预检”的方法时,预检请求会自动从浏览器发出。
最常见的预检方法是DELETE
从服务器中删除选定的文件或资产。
预检请求包括请求者的来源和所需的方法,使用 表示Access-Control-Request-Method
。
服务器分析预检请求以检查此来源是否有权执行此类方法。
如果是,则服务器返回源允许使用的所有方法,并指示您可以发送原始请求。
如果不是,则忽略原始请求。
然后,请求者浏览器可以缓存此预检批准,只要它有效。
您可以通过检查 的值来查看批准的到期日期Access-Control-Max-Age
。
然后,请求者浏览器可以缓存此预检批准,只要它有效。您可以通过检查 的值来查看批准的到期日期Access-Control-Max-Age
。
实施 CORS 的快速指南
要 开始使用 CORS,您必须在您的应用程序上启用它。以下是来自不同框架的精选代码,它们将使您的应用程序 CORS 准备就绪。
Nodejs Express 应用程序:
代码语言:javascript复制app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "YOUR-DOMAIN.TLD"); // update to match the domain you will make the request from
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.get('/', function(req, res, next) {
// Handle the get for this route
});
app.post('/', function(req, res, next) {
// Handle the post for this route
});
Flask:
Install package:
代码语言:javascript复制$ pip install -U flask-cors
然后将其添加到您的 Flask 应用程序中:
代码语言:javascript复制# app.py
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
cors = CORS(app)
Apache:
在服务器配置的 、 或 部分<Directory>
中添加<Location>
以下行。<Files>``<VirtualHost>
Header set Access-Control-Allow-Origin "*"
为确保正确应用更改,运行apachectl -t
然后使用重新加载 Apache sudo service apache2 reload
。
Kotlin 中的 Spring Boot 应用程序:
以下 Kotlin 代码块在 Spring Boot 应用程序上启用 CORS。
代码语言:javascript复制import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono
@Component
class CorsFilter : WebFilter {
override fun filter(ctx: ServerWebExchange?, chain: WebFilterChain?): Mono<Void> {
if (ctx != null) {
ctx.response.headers.add("Access-Control-Allow-Origin", "*")
ctx.response.headers.add("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS")
ctx.response.headers.add("Access-Control-Allow-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range")
if (ctx.request.method == HttpMethod.OPTIONS) {
ctx.response.headers.add("Access-Control-Max-Age", "1728000")
ctx.response.statusCode = HttpStatus.NO_CONTENT
return Mono.empty()
} else {
ctx.response.headers.add("Access-Control-Expose-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range")
return chain?.filter(ctx) ?: Mono.empty()
}
} else {
return chain?.filter(ctx) ?: Mono.empty()
}
}
}
Nginx:
以下代码块启用具有预检请求支持的 CORS。
代码语言:javascript复制#
# Wide-open CORS config for nginx
#
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
#
# Tell the client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
}
以上,就是关于跨域的一些介绍la