- 前端资源(html)- server-a 提供前端静态资源
- 后端数据(商品信息……)- server-b 提供基于http的api
#同源策略
当我们通过 xhr 的方式从一个源去获取另外一个源的数据的时候,就会触发同源策略的协议
**源**
域:协议+主机(ip、域名)+端口,比如 http://localhost:8888
**同源策略**
**浏览器**的安全设定,避免浏览器中通过脚本等方式去获取非同源的数据
官方文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
这套机制建立在一个核心内容基础之上:**http header**,这套机制定义了一系列的http头,通过这些http头来控制资源的访问。这些http头基本都是以 `access-control-?` 来进行命名的,不同的http头有不同的作用。
#普通资源请求
**[Access-Control-Allow-Origin](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS#access-control-allow-origin)**
控制当前允许访问该资源的源(origin)
# 非普通的资源请求
**简单请求 &非简单请求**
当请求满足一定规则的时候,为简单请求
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS#%E7%AE%80%E5%8D%95%E8%AF%B7%E6%B1%82
**预检请求**
如果当前请求满足了非简单请求,那么就会先发送一个 method 为 options 的请求(浏览器),称为**预检**,后端需要对这个预检请求进行处理,根据业务返回对应的头信息,来告知客户端是否允许发送非简单请求,我们需要在预检请求中返回一系列的头信息,来控制之前发送的非简单请求是否继续
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS#%E9%A2%84%E6%A3%80%E8%AF%B7%E6%B1%82
**附带身份凭证的请求**
cookie 实际也是会受到同源策略的限制的,如果非同源请求,cookie是默认被禁止携带处理的。
解决:
- 客户端在请求中要设置:withCredentials: true
- 服务端要在cors中设置:ctx.set('Access-Control-Allow-Credentials', 'true')
## 利用 koa-server-http-proxy 实现服务端代理
利用 koa-server-http-proxy 来处理正向代理
## 基于jwt鉴权(jsonwebtoken)
**cookie**
使用cookie来实现用户鉴权
- 目前cookie限制太多
- cookie会在一些情况下被禁用
**放置另外一个地方**
- 请求必须是可控的(ajax)
- 基于前后端开发的应用基本都是使用ajax方式进行
**jwt**
https://jwt.io/introduction
主流的 前后端分离模式 下,当前端调用后台接口时,由于是在非同一个域下的请求,从而会引发 浏览器 的自我安全保护机制,最终结果是 接口成功请求并响应 ,但 前端不能正常处理该返回数据 。
因此,当 同时满足 以下三个条件的情况下,就会出现跨域问题:
想要彻底解决跨域问题,只需要破坏以上三个条件的任一即可:
添加浏览器启动参数: chrome --disable-web-security ,但是极不推荐这种解决方式。
Jsonp,全称 JSON with Padding ,一种非官方的协议,而是一种约定;前端通过向后台发送 script 类型请求解决跨域,此时接口响应的 application/javascript 类型的数据会作为 callback 函数的参数进行处理。
所以,后台也需要做相应的处理。以 Java 为例,添加如下配置即可:
综上, jsonp 请求存在以下几个弊端:
用 Nginx 或 Apache 来代理调用方的请求( 客户端变更为相对路径请求,而非绝对路径 ),此时对于浏览器来说,由于请求是同源的,因此就不存在跨域问题。
以 Java 应用为例,添加如下全局配置:
如果只想针对某个类下的接口,或者是某个具体的接口配置允许跨域,只需要在相应的地方添加注解 @CrossOrigin 即可。
如果配置了 nginx 作为代理服务器,那么只需要为 nginx 添加支持跨域请求即可:
Q1:浏览器在执行跨域请求时,是先执行后判断,还是先判断后执行?
A1:都有可能,这需要根据所发送的请求是 简单请求 还是 非简单请求 来判断;如果是非简单请求,浏览器每次在执行真正的请求之前,还会先发送一个 options 请求方式的预检命令【 可设定缓存时长,取消每次请求都要预检,提高效率,参考上面的服务端配置 】。关于两种请求的区分及定义,参考下图说明:
Q2:如果是允许带( 被调用方 ) cookie 的跨域请求,此时服务端同样配置为 Access-Control-Allow-Origin 等于 * ,前端是否还可以请求成功?
A2:不可以,此时要将 Access-Control-Allow-Origin 指定为 调用方 具体的域【 可以先取得调用方的域再动态配置,这样就不存在多个域请求的限制问题 】,并且添加配置 Access-Control-Allow-Credentials 为 true 。
跨域问题来源于JavaScript的同源策略,即只有 协议+主机名+端口号 (如存在)相同,则允许相互访问。即JavaScript只能访问和操作自己域下的资源,不能访问和操作其他域下的资源。
注意:localhost和127.0.0.1也属于跨域。
如果Origin指定的源不在许可范围内, 服务器会返回一个不带有Access-Control-Allow-Origin字段的响应 . 浏览器解析时发现缺少了这个字段, 就会报错.
修改Django中的views.py文件修改views.py中对应API的实现函数,给返回值加上响应头Access-Control-Allow-Origin,允许其他域通过Ajax请求数据:
满足以下两个条件的请求。
(1) 请求方法是以下三种方法之一:
(2) HTTP的头信息不超出以下几种字段:
非简单请求就是复杂请求。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
预检请求为OPTIONS请求,用于向服务器请求权限信息的。
预检请求被成功响应后,才会发出真实请求,携带真实数据。
JSONP是JSON with Padding的略称。它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。
JSONP的实现步骤大致如下(参考了来源中的文章)
请求时,接口地址是作为构建出的脚本标签的src的,这样,当脚本标签构建出来时,最终的src是接口返回的内容
这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
JSONP使用注意
基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了(所以如今它也基本被淘汰了)。
前面讲了JSONP的实现原理,现在我们可以自己写JS来实现JSONP功能。
一般情况下,我们希望这个script标签能够动态的调用,而不是像固定在html里面所以没等页面显示就执行了,很不灵活。
我们可以通过页面的触发事件操作后,通过javascript动态的创建script标签,这样我们就可以灵活调用远程服务。实例如下:
为了更加灵活,上述我们将你自己在客户端定义的回调函数的函数名传送给服务端,服务端则会返回以你定义的回调函数名的方法,将获取的json数据传入这个方法完成回调。
如上,jQuery框架也当然支持JSONP,可以使用 $.getJSON(url,[data],[callback]) 方法。
与js实现的方式相比,我们并不要自己生成一个script标签,客户端也并不需要自己定义一个回调函数.
上述这种方法,很方便,不需要我们自己定义回调函数和指定回调函数名,但是,如果说我们想指定自己的回调函数名,或者说服务上规定了固定回调函数名该怎么办呢?
我们可以使用$.ajax方法来实现。如下例:
在上小节中jsonp: 'callbacks'就是定义一个存放回调函数的键,jsonpCallback是前端定义好的回调函数方法名,server端接受callback键对应值后就可以在其中填充数据打包返回。
但是,jsonpCallback参数可以不定义,jquery会自动定义一个随机名发过去,那前端就得用回调函数来处理对应数据了。利用jQuery可以很方便的实现JSONP来进行跨域访问。
如此,我们的跨域处理即完成,支持所有的请求。