August 4, 2019
By: Kevin

跨域访问

cros的定义

很多web程序员不够深入了解跨域访问, 上个月Zoom(就是那个刚上市的zoom)爆出本地zoomserver 跨域访问漏洞的问题暴露了程序员的无知.

有篇很有意思的文章,推荐阅读

https://fosterelli.co/developers-dont-understand-cors

跨域让人疑惑的地方是它是一个在浏览器实现的, 受服务器控制的限制策略(这句话优点拗口).

从浏览器端来看

跨域问题是由浏览器的安全策略所引起的. 为了保护用户的安全和隐私, 浏览器限制了不同源(origin)之间的交互. 源是由协议(如 HTTP 或 HTTPS)、主机名和端口号组成的.

浏览器的同源策略(Same-Origin Policy)是一种安全机制,它要求网页只能与同一源的资源进行交互,而不允许与不同源的资源进行直接的访问. 同源策略的目的是防止恶意网站获取用户的敏感信息或进行恶意操作.

在同源策略下,跨域请求是被禁止的. 当网页中的 JavaScript 代码通过 XMLHttpRequest、Fetch API、AJAX 等方式发送请求时,浏览器会检查请求的目标是否与当前页面的源相同. 如果目标与源不同,就会触发跨域问题.

跨域问题可以分为以下几种常见情况:

  • 域名不同:如果请求的域名与当前页面的域名不同,即使端口号和协议相同,也会被视为跨域请求.
  • 端口号不同:如果请求的端口号与当前页面的端口号不同,即使域名和协议相同,也会被视为跨域请求.
  • 协议不同:如果请求的协议与当前页面的协议不同(http vs https),即使域名和端口号相同,也会被视为跨域请求.

跨域问题的存在是为了确保浏览器的安全性. 但在某些情况下,我们确实需要进行跨域访问,例如前后端分离的开发模式,前端应用需要与不同域的后端 API 进行通信. 为了解决跨域问题,需要在服务端和客户端进行相应的配置,例如设置 CORS 头部、使用代理服务器等方法,以允许特定的跨域请求. 这样浏览器就会根据配置的规则来判断是否允许跨域访问,保证安全性的同时实现必要的跨域通信.

在Server端来看

为了限制能发起请求的域名, 服务器端设置响应头部,以控制跨域访问. 这通常是通过设置 CORS(Cross-Origin Resource Sharing)相关的头部信息来实现.

判断发起请求的域 设置响应头部:服务器端设置响应头部,以允许跨域访问. 这通常是通过设置 CORS(Cross-Origin Resource Sharing)相关的头部信息来实现的。

完整流程

cors

服务端实现

有了以上的理解, 服务端实现就变得非常简单, 即能够对请求消息进行的回应, 加上以上的头信息

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600

nodejs server

const express = require('express');
const app = express();

app.use('/api', (req, res) => {
  // 跨域配置
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
});


app.listen(80, () => {
  console.log('Server is running on port 80');
});

clojure server

  1. 在project.clj中增加依赖[ring-cors "0.1.13"]
  2. 在middleware.clj加入ring-cors中间件
(ns book-api.middleware
  (:require
   [book-api.env :refer [defaults]]
   [book-api.config :refer [env]]
   [ring-ttl-session.core :refer [ttl-memory-store]]
   [ring.middleware.cors :refer [wrap-cors]]  ; <---- import lib
   [ring.middleware.defaults :refer [site-defaults wrap-defaults]]))

(defn wrap-base [handler]
  (-> ((:middleware defaults) handler)
      (wrap-cors :access-control-allow-origin [#".*"]  ; <--- 增加中间件
                 :access-control-allow-methods [:get :put :post :delete])
      (wrap-defaults
       (-> site-defaults
           (assoc-in [:security :anti-forgery] false)
           (assoc-in  [:session :store] (ttl-memory-store (* 60 30)))))))

如果经过nginx转发呢?

分两种情况.

Nginx仅作转发

cors

不需要Nginx做额外的工作. Nginx的作用是充当反向代理服务器,将请求转发到目标服务器上,并将目标服务器的响应转发回浏览器.

通过Nginx来实现跨域

如果服务不支持跨域, 也可以通过Nginx中转来实现.

流程如下

cors

  1. 在nginx配置文件中添加如下代码:
location /api {
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, PUT';
    add_header 'Access-Control-Allow-Headers' 'X-Requested-With, Content-Type';

    # 配置反向代理
    proxy_pass http://backend_server_ip:port;
}

其中, /api 是接口地址,backend_server_ip 是后端服务器的IP地址,port 是端口号。

  1. 在上述代码中,使用add_header命令设置了跨域请求需要的响应头。Access-Control-Allow-Origin设置允许跨域的源,可以设置为 * 代表允许任意来源;Access-Control-Allow-Methods设置允许的请求方法;Access-Control-Allow-Headers设置允许的请求头。
  2. 配置反向代理,将跨域请求发送到后端服务器.

curl验证跨域

curl -I -H "Origin: http://from.com" http://to.com

如果在看在返回的结果中有

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

则跨域配置是成功的

Tags: clojure