问题描述

  • CSRF与XSS:

    • CSRF(跨域请求伪造):黑客网站盗用用户在A网站的cookie向A网站发送请求

      • 如何防御:

        1. Token验证(使用最多):服务器发送一个Token给客户端,客户端提交的表单请求要携带这个Token,如果Token不合法服务器拒绝这个请求

        2. 隐藏令牌:把Token隐藏在http的head头中,本质上与方法一没有太大区别

        3. Referer验证:Referer指的是页面请求来源。意思是只接受本站的请求,服务器才做响应

    • XSS(跨域脚本攻击):不需要你做任何登录认证,通过合法的操作(如在url输入、在评论框输入),向你的页面注入脚本(js、html)。主要分反射型和存储型攻击

      • 如何防御:

        1. 编码:对用户输入的数据进行HTML Entity编码,将脚本转换为纯文本,对特殊符号转义处理

        2. 过滤:移除用户输入数据的Script节点、iframe节点

  • 同源策略:同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓跨域访问指前端发起网络请求时与后端不在同一个ip下或者不在同一个端口下,如:

    • 127.0.0.1:8080 到 127.0.0.1:8000
    • 127.0.0.1 到 127.0.0.2

      都属于跨域访问。

  • 同源策略限制内容有:

    • Cookie、LocalStorage、IndexedDB等存储性内容
    • DOM节点
    • AJAX请求
      跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
  • 但是有三个标签是允许跨域加载资源的:

    • img
    • link
    • script

项目环境

  • Vue
  • Nginx
  • Django

跨域解决方案

jsonp

  • 原理:利用script标签没有跨域限制的漏洞,网页可以得到其他来源动态产生的json数据。但是仅支持get方法具有局限性,不安全可能会遭受XSS攻击

  • 实现流程:创建一个script标签,并设置一个回调函数,类似于前端设置好一个函数,后端返回一个执行该函数的命令

    1
    // 前端代码
    2
    function jsonp(url){
    3
        var frame = document.createElement('script');
    4
        frame.src = url;
    5
        $('body').append(frame);
    6
    });
    7
    8
    // 预先设置好的回调函数
    9
    function func(res){
    10
        alert(res.message+res.name+'你已经'+res.age+'岁了');
    11
    }
    1
    // 服务端代码
    2
    router.get('/article-list', (req, res) => {
    3
        console.log(req.query, '123');
    4
        let data = {
    5
            message: 'success!',
    6
            name: req.query.name,
    7
            age: req.query.age
    8
        }
    9
        data = JSON.stringify(data)
    10
        res.end('func(' + data + ')');
    11
    });

CORS

  • 原理:服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。

  • option请求:

    1. 在正式的请求前,浏览器会根据需要,发起一个“PreFlight”(也就是option请求),来让服务器返回允许的方法(如get、post),被跨域访问的Origin,或者是否需要Credentials(认证信息)

    2. 如果是简单请求则不会触发PreFlight,浏览器对简单请求的要求是:

      1. 只能是GET、HEAD、POST方法
      2. 除了浏览器自己在http头上加的信息(如Connection、User-Agent),开发者只能加:Accept、Accept-Language、Content-Type
      3. ContentType只能取这几个值:
        • application/x-www-form-urlencoded
        • multipart/form-data
        • text/plain
      4. 服务器可以设置Access-Control-Max-Age来控制浏览器在多长时间内无需再发送预检请求,从而减少不必要的预检请求
  • 解决方案

    1. 安装django-cors-headers,pip install django-cors-headers

    2. 修改 setting.py

      1
      INSTALLED_APPS = [
      2
          ...
      3
          'corsheaders'
      4
          ...
      5
      ] 
      6
      7
      # 添加中间件
      8
      MIDDLEWARE = [
      9
          'django.middleware.security.SecurityMiddleware',# 默认
      10
          'django.contrib.sessions.middleware.SessionMiddleware', # 默认
      11
      12
          'corsheaders.middleware.CorsMiddleware',# 新增 ✔
      13
          # 注意顺序
      14
          'django.middleware.common.CommonMiddleware', 
      15
      16
          'django.middleware.csrf.CsrfViewMiddleware',# 默认
      17
          'django.contrib.auth.middleware.AuthenticationMiddleware',# 默认
      18
          'django.contrib.messages.middleware.MessageMiddleware', # 默认
      19
          'django.middleware.clickjacking.XFrameOptionsMiddleware',# 默认
      20
          'django.middleware.common.CommonMiddleware',# 默认
      21
      ]
      22
      23
      # 跨域增加忽略
      24
      CORS_ALLOW_CREDENTIALS = True
      25
      CORS_ORIGIN_ALLOW_ALL = True
      26
      # 如果报错请注释这一句
      27
      CORS_ORIGIN_WHITELIST = (
      28
          '*'
      29
      )
      30
      CORS_ALLOW_METHODS = (
      31
          'DELETE',
      32
          'GET',
      33
          'OPTIONS',
      34
          'PATCH',
      35
          'POST',
      36
          'PUT',
      37
          'VIEW',
      38
      )
      39
      CORS_ALLOW_HEADERS = (
      40
          'XMLHttpRequest',
      41
          'X_FILENAME',
      42
          'accept-encoding',
      43
          'authorization',
      44
          'content-type',
      45
          'dnt',
      46
          'origin',
      47
          'user-agent',
      48
          'x-csrftoken',
      49
          'x-requested-with',
      50
      )
      51
      #部署到云服务上必备
      52
      ALLOWED_HOSTS = ['*']
    3. 如果需要携带cookie跨域,还需要在设置一下axios axios.defaults.withCredentials = true,切记cookie的domain要和发送请求的origin一致,不然会出现发送请求没有携带cookie的问题(注:localhost与127.0.0.1不等价)

postMessage

  • 原理:postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

    • 页面和其打开的新窗口的数据传递
    • 多窗口之间消息传递
    • 页面与嵌套的iframe消息传递
    • 上面三个场景的跨域数据传递
  • 实现方法

    1
    // a.html
    2
    <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
    3
    //内嵌在http://localhost:3000/a.html
    4
        <script>
    5
        function load() {
    6
            let frame = document.getElementById('frame')
    7
            frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据
    8
            window.onmessage = function(e) { //接受返回数据
    9
            console.log(e.data) //我不爱你
    10
            }
    11
        }
    12
        </script>
    1
    // b.html
    2
    window.onmessage = function(e) {
    3
        console.log(e.data) //我爱你
    4
        e.source.postMessage('我不爱你', e.origin)
    5
    }

使用代理服务器

  • nginx

    1. 修改nginx配置文件nginx.conf

      1
      server {
      2
          listen       81;
      3
          server_name  www.domain1.com;
      4
          location / {
      5
              proxy_pass   http://www.domain2.com:8080;  #反向代理
      6
              proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
      7
              index  index.html index.htm;
      8
      9
              # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
      10
              add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
      11
              add_header Access-Control-Allow-Credentials true;
      12
          }
      13
      }