跨域

同源策略

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。
同源是指 “协议+域名+端口” 三者相同。
即便两个不同的域名指向同一个ip地址,也非同源

  • 同源策略限制内容有:

    1. Cookie、LocalStorage等存储性内容
    2. DOM 节点
    3. AJAX 请求发送后,响应结果被浏览器拦截了
  • 允许跨域加载资源的标签:

    1
    2
    3
    <img src=XXX>
    <link href=XXX>
    <script src=XXX>

跨域

当协议、子域名、主域名、端口号中任意一个不相同时,都算不同域。不同域之间相互请求资源,称为跨域。
比如: 应用访问了该应用域名(或端口)以外的域名(或端口)。
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

浏览器如何判断该请求是不是跨域请求?

非跨域请求: 在请求头中,只包含请求的主机名(host)。
跨域请求: 在请求头中,会既包含要请求的主机名(host)还包括当前的源主机名(origin),如果两者不一致,那就是跨域请求。

解决跨域的方案

一、 客户端浏览器解除跨域限制(不现实)

浏览器是默认开启跨域安全检查的,可以使用命令行启动浏览器,加上禁止安全检查的参数

二、 JSONP

JSONP(JSON with Padding)是JSON的一种补充使用方式,利用 <script> 标签请求资源可以跨域的特点,来解决跨域问题。
JSONP请求的类型是JavaScript脚本,而XHR请求的类型是json类型。
特点:
仅支持get方法具有局限性, 不安全可能会遭受XSS攻击。服务端需要修改代码。
实现原理:
声明一个回调函数,函数名当做参数值,传递给跨域请求的服务器,函数形参为要获取目标数据(服务器返回的data)。
创建一个<script>标签,script的src为API接口地址,在这个地址中,向服务器传递该函数名(可以通过问号传参:?callback=show)
服务器接收到请求后,需要进行特殊的处理,接成一个字符串
最后服务器把数据返回给客户端,客户端再调用执行之前声明的回调函数,对返回的数据进行操作

三、CORS (Cross-origin resource sharing)

CORS 需要浏览器和后端同时支持。
浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求。只要后端实现了CORS接口,就可以跨域通信。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)

  1. 简单请求
    只要同时满足以下两大条件,就属于简单请求:
    • method: head post get 之一
    • header信息不超过这几个字段:
      Accept
      Accept-Language
      Content-Language
      Last-Event-ID
      Content-Type (值有限制)
    • Content-Type 的值仅限于下列三者之一:
      text/plain
      multipart/form-data
      application/x-www-form-urlencoded
      对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段
      Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。
      如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。
      浏览器发现,这个response header信息没有包含Access-Control-Allow-Origin字段,就知道出错了,
      从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。

tips: 这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段:

  • Access-Control-Allow-Origin (必须)
    要么是请求时Origin字段的值,要么是一个 *,表示接受任意域名的请求。

  • Access-Control-Allow-Credentials (可选)
    一个Boolean。表示是否允许发送Cookie(默认Cookie不包括在CORS请求之中)
    同时,前端开发必须在AJAX请求中打开withCredentials属性

    1
    2
    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
  1. 非简单请求
    非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json

    • 预检请求 (OPTIONS)
      在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)
      浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
      除了Origin字段,”预检”请求的头信息还包括的特殊字段:
      Access-Control-Request-Method 该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,eg:PUT。

    • 预检请求的回应

      • Access-Control-Allow-Origin
        关键字段。 若设为 *,表示同意任意跨源请求。
      • Access-Control-Allow-Methods: GET, POST, PUT
        该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法
      • Access-Control-Allow-Credentials
        是否允许发送cookie
      • Access-Control-Max-Age
        可选字段。单位为秒。代表在此期间内,不用发出另一条预检请求
    • 浏览器的正常请求和回应
      一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

四、node中间层代理

利用node进行接口转发,使用node + express + http-proxy-middleware搭建一个proxy服务器。
在代理服务器中修改响应头信息,实现跨域并允许带cookie:

1
2
3
4
5
6
// 如果要发送Cookie,Access-Control-Allow-Origin就不能设为*,
// 必须指定明确的、与请求网页一致的域名
res.header('Access-Control-Allow-Origin', '*')
res.header("Access-Control-Allow-Credentials", true)
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, OPTIONS')
res.header('Access-Control-Allow-Headers','自定义的请求头')

对于非简单请求的预检命令:OPTIONS类型的请求,直接返回200。

http-proxy-middleware里面配置的参数:

  • target 代表远程真实服务器的地址
  • changeOrigin设置为true,表示将请求转发到target地址上
  • pathRewrite 是对请求路径做处理, 例如可以将/api转换成/server/api.

拦截客户端的请求,转到目标服务器。
目标服务器的响应结果再返回给node服务器,node服务器再返回给浏览器.

node中间层代理的其他应用: 首屏加载
把首屏渲染的任务交给nodejs去做,次屏的渲染依然使用浏览器渲染。
服务端渲染,可直接拼接出html字符串,可以大大提高首屏渲染效率,减少用户等待时间。

五、Nginx 反向代理

打开 Nginx 的安装目录下的nginx.conf文件,只需要配置 http 模块下的 server 即可。

  • 修改http模块下的server配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    server {
    listen 80; # 端口号
    server_name localhost; # server name 默认 localhost

    #access_log logs/host.access.log main;

    location / { # 访问路径匹配规则
    root html;
    index index.html index.htm;
    }

    error_page 500 502 503 504 /50x.html; # 错误处理
    location = /50x.html {
    root html;
    }
    }

location 配置项定义了访问 Nginx 某路径时的匹配规则,后面紧跟的是匹配的路径。
可以直接写绝对路径,可以写正则匹配:

1
2
3
4
5
6
7
# 当访问 http://localhost/api1 时命中
location /api1 {
}

# 当访问 http://localhost/api2 和 http://localhost/api3 时命中
location ~ ^/(api2/api3) {
}
  • proxy_pass
    location 里配置项有很多,其中一个是 proxy_pass:
    意思是将当前命中的 Nginx 接口代理到其他 server 的接口。
    例如:将 http://localhost/api 代理到 https://baidu.com/api
    1
    2
    3
    location /api {
    proxy_pass https://baidu.com;
    }

需要注意的是,proxy_pass末尾的/:

1
2
3
4
5
6
7
location /api {
proxy_pass https://baidu.com/; # 将会被代理到 https://baidu.com/
}

location /api {
proxy_pass https://baidu.com; # 将会代理到 https://baidu.com/api
}
  • add_header
    Location 配置中的 add_header选项,表示 Nginx 将在 response 中添加一些额外的响应头信息给客户端
    1
    2
    3
    4
    5
    6
    7
    8
    9
    location /api {
    add_header Access-Control-Allow-Origin * always;
    add_header Access-Control-Allow-Headers *;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS";
    if ($request_method = 'OPTIONS') {
    return 200; # Nginx 拦截 OPTIONS 直接响应 200
    }
    proxy_pass https://baidu.com;
    }
查看评论