首页 > 技术文章 > 跨域

ZN-225 2019-06-11 17:27 原文

跨域

同源策略

  同源策略限制了从同一个源的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

  同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。

同源

  如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的源。

不受同源策略限制的

  • 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。
  • 跨域资源的引入是可以的。但是js不能读写加载的内容。如嵌入到页面中的<script src="..."></script>,<img>,<link>,<iframe>等。

JsonP

  JSONP的简单实现模式,或者说是JSONP的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。

  通过script标签来实现。

  后端代码

urlpatterns = [
    url(r'^abc/', views.abc),
]

def abc(request):
    return HttpResponse("rion()")

 

  前端代码

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="x-ua-compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>xyz</title>
</head>
<body>
<button id="b1">点我</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
  function rion() {
    console.log("盘他,盘圆润喽!");
  }
</script>
<script src="http://127.0.0.1:8002/abc/"></script>
</body>
</html>

  这样就可以简单实现跨域,前端打印结果为盘他,盘圆润喽!

  同样的,这种方式也是可以传参数的。

  后端代码

urlpatterns = [
    url(r'^abc/', views.abc),
]

def abc(request):
    res = {"code": 0, "data": ["SNIS-561", "SNIS-517", "SNIS-539"]}
    return HttpResponse("rion({})".format(json.dumps(res)))

  前端代码

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="x-ua-compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>xyz</title>
</head>
<body>
<button id="b1">点我</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
  function rion(res) {
    console.log(res);
  }
</script>
<script src="http://127.0.0.1:8002/abc/"></script>
</body>
</html>

  这样的话就可以实现传参的跨域。

动态传参

  后端代码

urlpatterns = [
    url(r'^abc/', views.abc),
]

def abc(request):
    res = {"code": 0, "data": ["SNIS-561", "SNIS-517", "SNIS-539"]}
    func = request.GET.get("callback")
    return HttpResponse("{}({})".format(func, json.dumps(res)))

  前端代码

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="x-ua-compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>xyz</title>
</head>
<body>
<button id="b1">点我</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
  function rion(res) {
    console.log(res);
  }
  function addScriptTag(src){
    var scriptEle = document.createElement("script");
    $(scriptEle).attr("src", src);
    $("body").append(scriptEle);
    $(scriptEle).remove();
  }
  $("#b1").click(function () {
    addScriptTag("http://127.0.0.1:8002/abc/")
  })
</script>
</body>
</html>

  点击b1按钮的时候,会在页面上插入一个script标签,然后从后端获取数据。

jQuery中的getJSON方法

  后端代码

urlpatterns = [
    url(r'^abc/', views.abc),
]

def abc(request):
    res = {"code": 0, "data": ["SNIS-561", "SNIS-517", "SNIS-539"]}
    func = request.GET.get("callback")
    return HttpResponse("{}({})".format(func, json.dumps(res)))

 

  前端代码

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="x-ua-compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>xyz</title>
</head>
<body>
<button id="b1">点我</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
  $("#b1").click(function () {
    $.getJSON("http://127.0.0.1:8002/abc/?callback=?", function (res) {
      console.log(res);
    })
  });
</script>
</body>
</html>

  要注意的是在url的后面必须要有一个callback参数,这样getJSON方法才会知道是用JSONP方式去访问服务,callback后面的那个?是jQuery内部自动生成的一个回调函数名。

  如果我们想自己指定回调函数名,或者说服务上规定了回调函数名该怎么办呢?我们可以使用$.ajax方法来实现:

  后端代码

urlpatterns = [
    url(r'^abc/', views.abc),
]

def abc(request):
    res = {"code": 0, "data": ["SNIS-561", "SNIS-517", "SNIS-539"]}
    func = request.GET.get("callback")
    return HttpResponse("{}({})".format(func, json.dumps(res)))

 

  前端代码

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="x-ua-compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>xyz</title>
</head>
<body>
<button id="b1">点我</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
  $("#b1").click(function () {
    $.ajax({
      url: "http://127.0.0.1:8002/abc/",
      dataType: "jsonp",
      jsonp: "callback",
      jsonpCallback: "rion2"
    })
  });
  function rion2(res) {
    console.log(res);
  }
</script>
</body>
</html>

 

  不过,我们通常将回调函数写在success回调中:

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="x-ua-compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>xyz</title>
</head>
<body>
<button id="b1">点我</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
  $("#b1").click(function () {
    $.ajax({
      url: "http://127.0.0.1:8002/abc/",
      dataType: "jsonp",
      success: function (res) {
        console.log(res);
      }
    })
  })
</script>
</body>
</html>

 

CORS

  CORS,跨域资源共享。CORS有两种请求,简单请求(simple request)和非简单请求(no simple request)。

简单请求判断

简单请求

  只要同时满足以下两大条件,就属于简单请求。

  请求方法是以下三种之一:

    • HEAD
    • GET
    • POST

  HTTP的头信息不超出以下几种字段:

    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • ContentType:只限于三个值application/x-www-form-urlencode、multipart/form-data、text/plain
处理方式

  在跨域场景下,当浏览器发送简单请求时,浏览器会自动在请求头中添加表明请求来源的 Origin 字段。

  后端程序只需要在返回的响应头中加上 Access-Control-Allow-Origin 字段,并且把该字段的值设置为 跨域请求的来源地址或简单的设置为 * 就可以了。

  可以在Django中间件中的process_response方法来给相应对象添加该字段。

from django.utils.deprecation import MiddlewareMixin


class CorsMiddleware(MiddlewareMixin):

    def process_response(self, request, response):
        # 给响应头加上 Access-Control-Allow-Origin 字段 并简单的设置为 *
        response['Access-Control-Allow-Origin'] = '*'
        return response

 

非简单请求

  对于非简单请求,浏览器通常都会在请求之前发送一次 OPTIONS 预检 请求,返回码是204,该请求会向后端服务询问是否允许从当前源发送请求并且询问允许的 请求方法 和 请求头字段。预检测通过才会真正发出请求,这才返回200。

  现在前端向后端发送PUT请求,具体的发送请求如下:

   解决犯法就是在后端简单的响应对象添加上常用的请求方法。

  django代码示例如下:

from django.utils.deprecation import MiddlewareMixin


class CorsMiddleware(MiddlewareMixin):

    def process_response(self, request, response):
        # 给响应头加上 Access-Control-Allow-Origin 字段 并简单的设置为 *
        response['Access-Control-Allow-Origin'] = '*'
        if request.method == 'OPTIONS':
            # 允许发送 PUT 请求
            response['Access-Control-Allow-Methods'] = 'PUT, DELETE'
            # 允许在请求头中携带 Content-type字段,从而支持发送json数据
            response['Access-Control-Allow-Headers'] = 'Content-type'
        return response
 django-cors-headers

   这个是一个处理cors跨域问题的包,我们只需要安装使用即可。

  安装

pip install django-cors-headers

  注册APP

INSTALLED_APPS = [
    ...
    'app01.apps.App01Config',
    'corsheaders',  # 将 corsheaders 这个APP注册
]

  添加中间件

  必须放在最前面,因为要解决跨域问题,只有允许跨域请求了,后续中间价才会正常执行。

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # 添加中间件
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

 

  配置

  可以选择不限制跨域访问

CORS_ORIGIN_ALLOW_ALL = True

  也可以设置允许访问的白名单

CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = (
    # '<YOUR_DOMAIN>[:PORT]',
    '127.0.0.1:8080'
)

代理

  如果我们请求的时候还是用前端的域名,然后有个东西帮我们把这个请求转发到真正的后端域名上,这样就避免跨域,这时候,Nginx出场了。

Nginx配置

server {
    # 监听9099端口
    listen 9099;
    
    # 域名是localhost
    server_name localhost;
    
    # 凡是localhost:9099/api这样子的,都转发到真正的服务端地址http://localhost:9871
    location ^~ /api {
        proxy_pass http://localhost:9871;
    }
}

 

参看资料

https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy

https://segmentfault.com/a/1190000015597029

https://www.cnblogs.com/liwenzhou/p/9513648.html

推荐阅读