您当前的位置:首页 > 计算机 > 服务器 > 网络服务

cors跨域

时间:11-21来源:作者:点击数:

一、问题

1.前端用ajax get访问接口,报错 : No 'Access-Control-Allow-Origin' header is present on the requested resource.

二、原因

javascript处于安全考虑,不允许跨域访问,CORS导致的。

三、CORS

3.1、什么是CORS?

CORS(跨域资源共享,Cross-Origin Resource Sharing)是一种跨域访问的机制,可以让Ajax实现跨域访问。是一种基于HTTP头的机制,该机制通过允许服务器标示除了它自己以外的其他源,使得浏览器允许这些域加载自己的资源。

  • 服务器:标识哪些源可以访问自己的资源
  • HTTP头:Access-Control-Allow-Origin: *     # 星号允许所有的源访问

                        Origin: https://bilibili.com             # 表明该请求来自哪个源

  • 浏览器:必须实现CORS相关的规范;是否载入都有浏览器控制

在服务器的response header中,加入"Access-Control-Allow-Origin:*" 即可以支持CORS,apache/nginx等怎么配置,见参考文档。

举个例子:

    * API部署在DomainA上;

    * Ajax文件部署在DomainB上,Ajax文件会向API发送请求,返回数据;

    * 用户通过DomainC访问DomainB的Ajax文件,请求数据

以上过程就发生了跨域访问。如果直接使用Ajax来请求就会失败,就像Chrome提示的:No 'Access-Control-Allow-Origin' header is present on the requested resource.

3.2、CORS的工作机制

先打开百度首页,然后在浏览器console下直接使用fetch打开淘宝的首页:

​
fetch('https://www.taobao.com')

浏览器清楚这是一个跨域请求,用CORS机制的,所以浏览器会检查返回内容的头部,是否包含了 access control allow origin这个标头

切换到Network查看接口,可知这个例子中,访问淘宝的响应头中没有 access control allow origin,也就是不允许任何跨域的请求,因此,浏览器拒绝载入该内容。

Network中有一个请求:

同样在百度首页载入淘宝的页面,但这次请求加入定制的标头,并且内容改成了XML:

const xhr = new XMLHttpRequest();
xhr.open("GET", "https://www.taobao.com");
xhr.setRequestHeader("X-PINGOTHER", "pingpong");
xhr.setRequestHeader("Content-Type", "apptication/xml");
xhr.send("<person><name>Arun</name></person>");

Network中有两个请求:

得到了同样的CORS错误,在预检阶段失败了。

总结:

    对于 简单请求,浏览器会直接请求服务端,然后检查返回内容头部的 access control allow origin,如果允许当前域,则正常的载入并展示返回内容,否则拒绝载入。

    对于 非简单请求,浏览器会发出一个option方法,来询问服务器端, 是否允许来自当前域的请求访问该资源,服务器会返回允许访问的域HTTP方法以及允许的标头header,浏览器会进行判断后,决定是否发出真正的请求,这个询问的过程称为预检

3.3、简单请求与非简单请求的概念

设计后端请求时,将占用服务器过多资源的请求,通过预检可以降低服务器的负载,应该设计成非简单请求的服务。而轻量服务,则使用简单请求,提升响应速度。

# 简单请求请求
fetch('https://www.taobao.com')

# 预检请求,因为改动了请求头,不满足CORS简单请求的第二条要求
fetch('https://www.taobao.com', {
    headers: {
        a: 1,
    },
});

# POST请求,简单请求
fetch('https://www.taobao.com', {
    method: 'POST',
    body: JSON.stringify({a: 1, b: 2}),
});

# 预检请求,因为改动了请求头content-type,不满足CORS简单请求的第三条要求
fetch('https://www.taobao.com', {
    method: 'POST',
    headers: {
        'content-type': 'application/json',
    },
    body: JSON.stringify({a: 1, b: 2}),
});
​

3.4、为什么需要CORS?

  1. 保护服务器, 控制哪些域有权访问资源,有助于减轻服务器的负载与风险;
  2. 保护隐私,CORS限制了来自不同域的敏感数据的访问。例如,一个网站不能在没有用户授权的情况下,从其他域获取用户的个人信息;
  3. 安全考虑, 防止盗取用户数据或进行 CSRF攻击;
  4. 因为有实际的跨域请求需求,所以也不能完全禁止跨域请求;

四、如何解决Ajax跨域访问问题?

解决跨域问题有以下几种方法:

  1. 同源策略(增加反向代理)
  2. 使用jsonp
  3. 使用django-cors-headers
  4. 修改response header中,加入"Access-Control-Allow-Origin:*"
  5. nginx层配置“Access-Control-Allow-Origin *

使用jsonp方法,需让服务端返回jsonp格式的response,如:Returning JSON/JSONP from a Django view with a little decorator help (Example)

使用CORS:这个用起来比较方便,现在大多数浏览器都支持了,且我web服务器完全开放给别人调用,所以比较推荐CORS。

4.1、使用JSONP

使用Ajax获取json数据时,存在跨域的限制。不过,在Web页面上调用js的script脚本文件时却不受跨域的影响,JSONP就是利用这个来实现跨域的传输。因此,我们需要将Ajax调用中的dataType从JSON改为JSONP(相应的API也需要支持JSONP)格式。

JSONP只能用于GET请求。

4.2、使用django-cors-headers

pip install django-cors-headers

修改setting.py:

​INSTALLED_APPS = [
     'corsheaders' # 尽量放在前面
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware'  # 需要添加在CommonMiddleware中间件前面(必须)
]

# 添加白名单:设置可以任意访问,默认为False
CORS_ORIGIN_ALLOW_ALL =True

# 也可以通过白名单列表配置允许跨域访问的地址
CORS_ORIGIN_WHITELIST = (
    'localhost:3306',
    'http://localhost:8080'
)

# CORS_ALLOW_CREDENTIALS允许跨域时携带Cookie,默认为False
CORS_ALLOW_CREDENTIALS = True
​

4.3、在服务器的response header中,加入“Access-Control-Allow-Origin: *”

修改视图函数view.py,允许其他域通过Ajax请求数据:

def myview(_request):
  response = HttpResponse(json.dumps({"key": "value", "key2": "value"}))
  response["Access-Control-Allow-Origin"] = "*"
  response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"
  response["Access-Control-Max-Age"] = "1000"
  response["Access-Control-Allow-Headers"] = "*"
  return response

4.4、nginx配置

location / {
    # 跨域请求
    add_header Access-Control-Allow-Origin '*';
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers '*';

    # 发起预检请求,从而获知服务器是否允许该跨域请求
    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

总结一下预检请求通过的条件

  1. OPTIONS 方法获取的Response header 字段中,有Access-Control-* 的字段。表示服务器告知浏览器服务端支持跨域访问的方法和header 字段等。
  2.  复杂请求中的request method、request header、Origin等满足第1点中服务端告知给客户端的服务器端接受的条件。
  3. 当前两点都满足的时候,浏览器会才会发起跨域请求,否则浏览器直接拦截跨域请求,该请求不会走到服务器端。

其他补充

  • Access-Control-Max-Age 可以指定将预检请求的结果缓存多长时间,在这个时间范围内就不用再重复发起预检请求了
  • Access-Control-Allow-Credentials true 是否允许在跨域请求中传递cookie ,默认false
  • Access-Control-Allow-Origin 要根据实际允许跨域的origin填写,* 表示允许所有origin 跨域访问
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐