You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
'use strict';constvary=require('vary');/** * 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} credentials `Access-Control-Allow-Credentials` * - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown * @return {Function} cors middleware * @api public */module.exports=function(options){constdefaults={allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',};// 默认的配置项和使用时设置的options进行一个融合options=Object.assign({},defaults,options);// 因为函数的一些参数,exposeHeaders,allowMethods,allowHeaders的形式既可以是String,也可以是Array类型,// 如果是Array类型,也转换为用逗号分隔的字符串。if(Array.isArray(options.exposeHeaders)){options.exposeHeaders=options.exposeHeaders.join(',');}if(Array.isArray(options.allowMethods)){options.allowMethods=options.allowMethods.join(',');}if(Array.isArray(options.allowHeaders)){options.allowHeaders=options.allowHeaders.join(',');}if(options.maxAge){options.maxAge=String(options.maxAge);}options.credentials=!!options.credentials;options.keepHeadersOnError=options.keepHeadersOnError===undefined||!!options.keepHeadersOnError;returnasyncfunctioncors(ctx,next){// If the Origin header is not present terminate this set of steps.// The request is outside the scope of this specification.constrequestOrigin=ctx.get('Origin');// Always set Vary header// https://github.com/rs/cors/issues/10ctx.vary('Origin');// 如果请求头不存在 origin,则直接跳出该中间件,执行下一个中间件if(!requestOrigin)returnawaitnext();// 对origin参数的不同类型做一个处理letorigin;if(typeofoptions.origin==='function'){origin=options.origin(ctx);if(origininstanceofPromise)origin=awaitorigin;if(!origin)returnawaitnext();}else{origin=options.origin||requestOrigin;}constheadersSet={};functionset(key,value){ctx.set(key,value);headersSet[key]=value;}/** * 非OPTIONS请求的处理 * */if(ctx.method!=='OPTIONS'){// Simple Cross-Origin Request, Actual Request, and Redirectsset('Access-Control-Allow-Origin',origin);if(options.credentials===true){set('Access-Control-Allow-Credentials','true');}if(options.exposeHeaders){set('Access-Control-Expose-Headers',options.exposeHeaders);}if(!options.keepHeadersOnError){returnawaitnext();}try{returnawaitnext();}catch(err){consterrHeadersSet=err.headers||{};constvaryWithOrigin=vary.append(errHeadersSet.vary||errHeadersSet.Vary||'','Origin');deleteerrHeadersSet.Vary;err.headers=Object.assign({},errHeadersSet,headersSet,{vary: varyWithOrigin});throwerr;}}else{// Preflight Request// If there is no Access-Control-Request-Method header or if parsing failed,// do not set any additional headers and terminate this set of steps.// The request is outside the scope of this specification.if(!ctx.get('Access-Control-Request-Method')){// this not preflight request, ignore itreturnawaitnext();}ctx.set('Access-Control-Allow-Origin',origin);if(options.credentials===true){ctx.set('Access-Control-Allow-Credentials','true');}if(options.maxAge){ctx.set('Access-Control-Max-Age',options.maxAge);}if(options.allowMethods){ctx.set('Access-Control-Allow-Methods',options.allowMethods);}letallowHeaders=options.allowHeaders;if(!allowHeaders){allowHeaders=ctx.get('Access-Control-Request-Headers');}if(allowHeaders){ctx.set('Access-Control-Allow-Headers',allowHeaders);}ctx.status=204;}};};
目录
跨域
为什么会有跨域问题?
这是浏览器的同源策略所造成的,同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
如何解决跨域?
相信大家对于以上的解决方法都很熟悉,这里不再对每一种方法展开讲解,接下来主要讲一下CORS;
简单请求和非简单请求
浏览器将CORS跨域请求分为简单请求和非简单请求;
只要同时满足一下两个条件,就属于简单请求
(1)使用下列方法之一:
(2)请求的Heder是
不同时满足上面的两个条件,就属于非简单请求。
浏览器对这两种的处理,是不一样的。
简单请求
例子
对于简单请求,浏览器直接发出CORS请求。具体来说,就是头信息之中,增加一个Origin字段。
上面这个例子,
post
请求,Content-Type
为application/x-www-form-urlencoded
,满足简单请求的条件;响应头部返回Access-Control-Allow-Origin: http://127.0.0.1:3000
;浏览器发现这次跨域请求是简单请求,就自动在头信息之中,添加一个
Origin
字段;Origin
字段用来说明请求来自哪个源(协议+域名+端口号)。服务端根据这个值,决定是否同意本次请求。CORS请求相关的字段,都以
Access-Control-
开头Origin
字段的值*
:接受任何域名Access-Control-Allow-Origin
不能设置为*
,必须指定明确的,与请求网页一致的域名。withCredentials 属性
CORS请求默认不发送Cookie和HTTP认证信息,如果要把Cookie发到服务器,一方面需要服务器同意,设置响应头
Access-Control-Allow-Credentials: true
,另一方面在客户端发出请求的时候也要进行一些设置;非简单请求
非简单请求就是那种对服务器有特殊要求的请求,比如请求方法为
PUT
或DELETE
,或者Content-Type
字段为application/json
;1. 预检请求和回应
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为“预检”请求;
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段,只有得到肯定答复,浏览器才会发出正式的接口请求,否则就会报错;
HTTP请求的方法是POST,请求头
Content-Type
字段为application/json
。浏览器发现,这是一个非简单请求,就自动发出一个预检
请求,要求服务器确认可以这样请求。1.1预检请求
预检
请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin
,表示请求来自哪个域。除了
Origin
,预检
请求的头信息包括两个特殊字段:POST
Content-Type
;1.2预检回应
服务器收到
预检
请求以后,检查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,确认允许跨域请求,就可以做出回应。上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://127.0.0.1:3000可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。
如果浏览器否定了“预检”请求,就会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段,这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获h。
服务器回应的其他CORS字段
预检
请求。Access-Control-Request-Headers
字段,则Access-Control-Allow-Headers
字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在预检
中请求的字段。2.正常请求和回应
一旦服务器通过了
预检
请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin
头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段;服务端如何设置CORS
单独接口单独处理
比如一个简单的登录页面,需要给接口接口传入 username和password 两个字段;前端的域名为 localhost:8900,后端的域名为 localhost:3200,构成跨域。
1. 如果设置请求头
'Content-Type': 'application/x-www-form-urlencoded'
,这种情况则为简单请求;会有跨域问题,直接设置 响应头
Access-Control-Allow-Origin
为*
, 或者具体的域名;注意如果设置响应头Access-Control-Allow-Credentials
为true
,表示要发送cookie
,则此时Access-Control-Allow-Origin
的值不能设置为星号,必须指定明确的,与请求网页一致的域名。2. 如果设置请求头
'Content-Type': 'application/json'
,这种情况则为非简单请求处理OPTIONS请求,服务端可以单独写一个路由,来处理
login
的OPTIONS的请求大家都知道前端调用服务端的时候,会调用很多个接口,并且每个接口处理跨域请求的逻辑是完全一样的,我们可以把这部分抽离出来,作为一个中间件;
写一个中间件进行处理
首先了解一下koa中间件的“洋葱圈”模型
将洋葱的一圈看做是一个中间件,直线型就是从第一个中间件走到最后一个,但是洋葱圈就很特殊了,最早use的中间件在洋葱最外层,开始的时候会按照顺序走到所有中间件,然后按照倒序再走一遍所有的中间件,相当于每个中间件都会进入两次,这就给了我们更多的操作空间。
输出
Koa官方文档上把外层的中间件称为“上游”,内层的中间件为“下游”。
一般的中间件都会执行两次,调用
next
之前为一次,调用next
时把控制按顺序传递给下游的中间件。当下游不再有中间件或者中间件没有执行next
函数时,就将依次恢复上游中间件的行为,让上游中间件执行next
之后的代码;处理跨域的中间件简单示例
@koa/cors是怎么实现的
以上是 @koa/cors V3.0.0的源码实现,如果你真正理解的CORS,看源码的逻辑就会非常轻松。
主要是分两个逻辑来处理,有预检请求的和没有预检请求的。
对于非OPTIONS请求的处理,要根据情况加上
Access-Control-Allow-Origin
,Access-Control-Allow-Credentials
,Access-Control-Expose-Headers
这三个响应头部;对于OPTIONS请求(预检请求)的处理,要根据情况加上
Access-Control-Allow-Origin
,Access-Control-Allow-Credentials
,Access-Control-Max-Age
,Access-Control-Allow-Methods
,Access-Control-Allow-Headers
这几个响应头部;The text was updated successfully, but these errors were encountered: