CORS(Cross-Origin Resource Sharing,跨源资源共享)是一种浏览器技术,允许或限制从一个源(域名、协议和端口)请求另一个源的资源。它是一种用于解决跨域请求问题的机制。
背景
在Web开发中,出于安全考虑,浏览器默认会实施同源策略(Same-Origin Policy),即限制从一个源加载的文档或脚本如何与来自不同源的资源进行交互。这意味着,如果你在https://example.com
上运行的脚本,默认不能请求https://anotherdomain.com
上的资源。
CORS 的工作原理
CORS通过HTTP头部来描述哪个源可以访问资源。服务器可以通过设置特定的CORS头部来允许来自不同源的请求。以下是一些常用的CORS相关的HTTP头部:
-
Access-Control-Allow-Origin: 指定哪些来源可以访问资源。可以是特定的源,或使用
*
表示允许所有源。
Access-Control-Allow-Origin: https://example.com
-
Access-Control-Allow-Methods: 指定允许的方法,如GET、POST等。
Access-Control-Allow-Methods: GET, POST
-
Access-Control-Allow-Headers: 指定哪些请求头可以在实际请求中使用。
Access-Control-Allow-Headers: Content-Type
-
Access-Control-Allow-Credentials: 指定是否允许发送凭证(如Cookies)。如果设置为
true
,需要明确指定源而不能使用*
。 -
Access-Control-Expose-Headers: 指定哪些响应头可以在请求中暴露给页面脚本。
跨域请求类型
CORS处理两种基本的请求类型:
-
简单请求(Simple Requests): 只包含GET、POST、HEAD方法,并且HTTP头部限制在某些安全的选项(如Content-Type为
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)。 -
预检请求(Preflight Requests): 当请求使用了非简单方法(如PUT、DELETE,或者自定义的头部时),浏览器会先发送一个OPTIONS请求,以确定服务器是否允许该实际请求。
我们在实际开发中经常会遇到跨域问题,这次在公司的开发中使用midway开发时,有一个需求是A站的一个接口需要B站去调用,但是midway中 KOA框架和Egg框架解决跨域问题的方法不一样,查看@koa/cors
文档可以发现其使用方法:
import {
createConfiguration,
hooks,
} from '@midwayjs/hooks';
import * as Koa from '@midwayjs/koa';
import cors from '@koa/cors';
export default createConfiguration({
imports: [
Koa,
hooks({
middleware: [
cors({ origin: '*' }),
],
}),
],
});
/**
* CORS middleware
*
* @param {Object} [options]
* - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header
* - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH'
* - {String|Array} exposeHeaders `Access-Control-Expose-Headers`
* - {String|Array} allowHeaders `Access-Control-Allow-Headers`
* - {String|Number} maxAge `Access-Control-Max-Age` in seconds
* - {Boolean|Function(ctx)} credentials `Access-Control-Allow-Credentials`, default is false.
* - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown
* @return {Function} cors middleware
* @api public
*/
而egg封装了一个插件,egg-cors.一般默认的配置如下:
// {app_root}/config/config.default.js
exports.cors = {
origin: '*',
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
};
如果想要服务器允许所有站点访问,设置为origin:"*"即可,这代表了Acess-Control-Allow-Origin的Header为*,但是以上为简单情况,一般的CORS规范为两类,一种是简单请求,另一种为带预检的请求。
“预检请求”通常与跨域资源共享(CORS)相关。CORS 是一种浏览器安全机制,用于控制网站如何与其他域的资源进行交互。当浏览器发送跨域请求时,可能会先发送一个“预检请求”(preflight request),以确认实际请求是否安全。
预检请求的工作原理
- 请求方法:预检请求通常使用 HTTP OPTIONS 方法。
- 目标:该请求发送到目标服务器,以询问是否允许特定的跨域请求。
- 头部信息:预检请求中会包含一些特别的头部信息,如:
Origin
:发起请求的源(域名)。Access-Control-Request-Method
:实际请求的方法,比如GET
、POST
等。Access-Control-Request-Headers
:实际请求中使用的自定义头部信息。
服务器响应
服务器需要根据这些信息返回响应,以告知浏览器是否允许实际请求。在响应中,可以包括以下头部信息:
Access-Control-Allow-Origin
:允许访问的源。Access-Control-Allow-Methods
:允许的请求方法。Access-Control-Allow-Headers
:允许的自定义头部。
示例
假设你有一个前端应用在 https://example.com
,它尝试发送一个 POST 请求到 https://api.another-domain.com
。浏览器首先可能会发送一个预检请求,如下所示:
OPTIONS /api/resource HTTP/1.1
Host: api.another-domain.com
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
如果服务器允许这个请求,它可能会返回如下响应:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
在这种情况下,浏览器会继续执行实际的 POST 请求
一般我们的node应用提供的api都会配置安全限制,可以通过白名单的方式开启:
config.security = {
domanWhiteList: ['.A.com', '.B.com']
}
但是这样在带预检的请求时,还会存在问题,所以需要配置一个
exports.cors = {
credentials: true
}
这样就会可以正常访问请求。这是为什么呢?
下次说。。。