浏览器跨域机制

2022-07-13
7 min read

什么是跨域请求?

  1. 同域请求&跨域请求

    页面地址与页面内资源请求地址进行对比。当且仅当两者的协议、域名、端口都相同时,说资源请求是一个同域请求。
    img

  2. 协议相同

    页面地址 接口地址 跨域原因
    http://local.neoemacs.com.cn https://local.neoemacs.com.cn 协议不同
  3. 域名相同

    页面地址 接口地址 跨域原因
    http://local.neoemacs.com.cn http://any.neoemacs.com.cn 域名不同
  4. 端口相同

    页面地址 接口地址 跨域原因
    http://local.neoemacs.com.cn http://local.neoemacs.com.cn:81 端口不同

为什么浏览器有跨域机制

  • 跨域机制是浏览器的一种安全策略,保护服务器的资源安全
  1. XSS 攻击

  2. Cookie 的跨域安全策略

    • cookie 跨域共享策略,限制同源的数据才能共享 cookie
    • cookie 是网站自己的信息资源,是否允许发送给其他网站默认被浏览器强管控
    • cookie 的基本属性
      • httpOnly 仅允许 http 请求发送,不允许 js 脚本获取,实际上是防 XSS 攻击
      • domain(默认本网站域名)
      • path(默认 /);
      • max-age (默认值 -1)

跨域机制触发的表现

  • 真正的请求发送不出去
    • 举例:前端 HXR/Fetch 随意设置一个自定义 header name:比如 mdd
  • 请求已发送,但响应 body 数据不能被浏览器获取
    • 举例:针对垮域请求,后端只处理 option 请求、但不处理真实请求
  • 请求已发送,但响应 Header 不能被浏览器 JS 读取
    • 举例:JS 获取后端返回的 responseHeader(前提:未设置 Access-Control-Expose-Headers)

测试环境搭建

img

  1. 代码例子

    1. nginx 配置

      • nginx 启动

        nginx -c /Users/van/Desktop/0310/nginx.conf
        
      • 代码

        events {
            accept_mutex on;
            multi_accept on;
            worker_connections  24;
        }
        
        http {
        
            server {
                listen 443 ssl;
                server_name local.neoemacs.com.cn;
            ssl_certificate      /Users/van/Desktop/0310/ssl/server.crt;
                ssl_certificate_key  /Users/van/Desktop/0310/ssl/server.key;
        
                location /0310 {
                    alias '/Users/van/Desktop/0310';
                }
        
            }
        
            server {
            listen 8088 default_server;
                server_name local.neoemacs.com.cn;
        
                location /0310 {
                    alias '/Users/van/Desktop/0310';
                }
        
            }
        }
        
    2. html 代码

      访问地址:http://local.neoemacs.com.cn:8088/0310/demo.html

      • 代码

        <html>
        <h1>cross origin</h1>
        <input value='send xhr' onclick='sendxhr()' type='button'></input>
        <script >
        function sendxhr() {
            var xhr = new XMLHttpRequest();
            xhr.open('POST', 'https://www.jianshu.com/shakespeare/v2/notes/b722f61bb09f/audio', true);
            xhr.setRequestHeader('Content-type', 'application/json')
            xhr.send();
        }
        
        </script>
        </html>
        

跨域请求时序图

  • 时序图

    img

  1. 简单请求

    • 简单请求,Head 头仅包含如下 4 个(并且其值需要满足特定条件),value 长度是否限制?简单请求可直接访问,与同源请求一样:
      • https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
      • Accept:
        • 主要数据格式/次要数据格式;参数,只能是固定值否则浏览器不允许发送请求,用户无法随意构造
        • i.e. Accept:application/json
        • CORS-safelisted request header yes, with the additional restriction that values can’t contain a CORS-unsafe request header byte: 0x00-0x1F (except 0x09 (HT)), “():<>?@[\]{}, and 0x7F (DEL).
      • Accept-Language:
        • Accept-Language 表示浏览器所支持的语言类型;
        • i.e Accept-Language: zh-cn,zh;q=0.5, zh-cn 表示简体中文;zh 表示中文; q 是权重系数,范围 0 < q < 1,q 值越大,请求越倾向于获得其“;”之前的类型表示的内容,若没有指定 q 值,则默认为 1,若被赋值为 0,则用于提醒服务器哪些是浏览器不接受的内容类型。
        • CORS-safelisted request header yes, with the additional restriction that values can only be 0-9, A-Z, a-z, space or *,-.;=.
      • Content-Language:
        • 指明 body 的语言类型
        • i.e Accept-Language: zh-cn,zh;q=0.5
        • CORS-safelisted request header yes, with the additional restriction that values can only be 0-9, A-Z, a-z, space or *,-.;=.
      • Content-Type :
        • 主要数据格式/次要数据格式;参数,只能是固定值否则浏览器不允许发送请求,用户无法随意构造
        • CORS-safelisted :
          • application/x-www-form-urlencoded
          • multipart/form-data
          • text/plain
    • 简单请求,使用不同的 jsAPI 表现不一样,比如 axios 与 fetch 就不一样,似乎 axios 的表现更接近官方说法
  2. 预检请求

    • 浏览器修改请求方法为 OPTION 请求,尝试获取服务器对资源管理的策略。
    • OPTION 请求会屏蔽 requestBody,以及 request Header 信息。
    • 预检请求可以认为是对服务器资源的一种保护,

垮域响应头

  1. Access-Control-Allow-Origin

    • 允许的域,格式:协议:域名:端口,只能设置 1 个值,或者全通配符 *
  2. Access-Control-Allow-Credentials

    • 是否允许发送 cookie,如果是,Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers 均不允许为通配符
    • 一般不允许设置为 true,cookie 不容易控制要发送 cookie 将其转换为 header 再设置 Access-Control-Allow-Origin 控制更精准
  3. Access-Control-Allow-Methods

    • 允许的方法,多个用逗号分隔,或者使用通配符 *
  4. Access-Control-Max-Age

  5. Access-Control-Allow-Headers

    • 允许的请求头,多个用逗号分隔,或者使用通配符
  6. Access-Control-Expose-Headers

    • 前端 JS 允许读取的响应头,多个用逗号分隔,或者使用通配符

代码测试

  1. springmvc,单链路

    img

    @RequestMapping(path = "test/do", method = { RequestMethod.OPTIONS })
    public void doing() {
        log.info("\n==== [options]");
    }
    
    // @GetRequestMapping(path = "test/do", method = { RequestMethod.GET })
    @GetMapping(path = "test/do" )
    public User doings() {
        log.info("\n==== [get]");
        User user = new User();
        user.setName("vanniuner");
        // response.addHeader("Access-Control-Allow-Headers", "User-Agent");
        response.addHeader("Access-Control-Allow-Origin", "http://local.neoemacs.com.cn:8088");
        response.addHeader("Access-Control-Allow-Headers", "mmd");
        return user;
    }
    
    • 跨域请求错误的一种情况:真实请求被浏览器拦截,真实请求没发出去

    • SpringMVC @GetMapping 与 @RequestMapping 不需要额外声明 OPTIONS

    • SpringMVC 如果同时声明 2 个 mvc 函数,一个是 OPTIONS,另外一个是 GET,手动写的 OPTIONS 方法视为无效

    • 跨域请求错误的一种情况:请求发送成功,但响应数据被浏览器拦截

    • 性能问题,跨域请求是否有必要注意,mvc 函数体内的程序被执行了 2 次?需要注意,option 请求仅返回跨域 header 即可,需要特殊处理

    • 后端有无必要识别一个请求是跨域请求?无必要,在网站内发起 ajax 请求做控制主要是为了防止 XSS 攻击。

    • 设置多个相同的跨域响应头,仅针对 Access-Control-Allow-Origin 这种只允许设置一个值的情况需要注意,设置多个会出问题,尤其是在多链路情况下

      @Autowired
      HttpServletRequest request
      
      response.addHeader("Access-Control-Allow-Origin", "http://local.neoemacs.com.cn:8088");
      response.setHeader("Access-Control-Allow-Origin", "http://local.neoemacs.com.cn:8088");
      
    • 有 cookie 的情况

      • 跨域发送 cookie 服务器需要设置 Access-Control-Allow-Credentials:true,这实际也是对服务器的一种保护(默认情况下减少请求数据量)
      • Access-Control-Allow-Credentials:true 情况下,其他跨域响应头(Access-Control-Allow-Origin)不允许出现通配符 ,安全系数极大降低
    • 跨域响应头是否支持半通配符?比如 Access-Control-Allow-Origin : http://*.neoemacs.com.cn:8081 、Access-Control-Allow-Headers: mmd*,xs*

      • 不支持
    • 跨域请求错误的一种情况:请求发送成功,但响应头信息被浏览器拦截

  2. 不同浏览器实现的标准不同

    1. IE

      • 内核:微软 旗下 Trident 内核,也是俗称的 IE 内核;
      • Forbidden header name
      • User-Agent
    2. chrome

      • 内核:google 旗下 Webkit 的 分支内核 Blink
      • Forbidden header name
      • 设置了也无效
      • User-Agent
        • 设置了也无效
    3. firefox

      • 内核:Mozilla 旗下 Gecko
      • Forbidden header name
      • User-Agent
    4. safari

      • 内核:Applpe 旗下:基于 Webkit 内核维护
      • Forbidden header name
      • 设置有效果,需要服务器添加,Access-Control-Allow-Headers
      • User-Agent
      • 可设置,但需要服务器添加,Access-Control-Allow-Headers
    5. chrome 与 safari 浏览器的差异

      设置 user-agent 请求头的差异

    6. fetch 与 axios 的差异

      content-type

  3. springGateway,多链路

    img

    • 设置多个相同的跨域请求头,api 不一样
      • springboot 设置了,Access-Control-Allow-Origin;springGateway 也设置了 Access-Control-Allow-Origin, 将导致接口无法访问

        ServerHttpResponse response
        
        responseHeaders.remove("Access-Control-Allow-Credentials");
        responseHeaders.set("Access-Control-Allow-Credentials", "true");
        

其他问题

  • header 头不允许超过 128 个字符长度
  • 垮域响应头不能设置多个,网关处添加的处理需要先清理,再设置
  • 垮域资源限制不止控制接口,还包括静态资源,图片、字体、js、css 等等

正确的跨域处理逻辑

  • 判断 requestHeader Origin, 仅合法的才放行
  • 响应头设置 Access-Control-Allow-Origin = requestorigin
  • 响应头设置 Access-Control-Allow-Methods
  • 响应头设置 Access-Control-Allow-Headers 和 Access-Control-Expose-Headers 并支持可扩展,不设置为*
  • 响应头设置 Access-Control-Allow-Credentials 为 true
  • 响应头设置 Access-Control-Max-Age,使得浏览器缓存行为更加可控
  • 识别出 OPTIONS 请求后进行请求拦截,阻断业务逻辑执行
  • 识别出其他请求,则正常执行返回业务数据

跨域请求术语

CORS(Cross-Origin Resource Sharing)

  1. HXR

  2. Fetch Apis

  3. Same-origin_policy

    • 同源策略
      https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
    • The same-origin policy is a critical security mechanism that restricts how a document or script loaded by one origin can interact with a resource from another origin.
      • 它有助于隔离潜在的恶意文件,减少可能的攻击向量。 例如,它可以防止 Internet 上的恶意网站在浏览器中运行 JS,以从第三方 Webmail 服务(用户已登录)或公司 Intranet(其免受攻击者的直接访问)读取数据 没有公共 IP 地址)并将该数据中继到攻击者。
  4. CORS

    • 跨域资源共享机制
    • Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.
  5. simple Request

  6. CORS preflight.

  7. CORS-safelisted request header

  8. Forbidden header name

    http://center-test.neoemacs.com.cn/api/mall-gateway/services/product/api/purchase-product-skus/state?sort=createdDate,desc&page=0&size=20&state.equals=ONLIN
    http://center-test.neoemacs.com.cn/api/mall-gateway/services/admin/api/company-relations-list?sort=createdDate,desc&page=0&size=20&type.equals=SUPPLIER

  9. User-Agent