什么是跨域问题?如何解决?

时间:2021-11-17 01:16:09

什么是跨域呢?

在了解跨域之前,应该先了解一下什么叫做同源策略

同源策略指的是:浏览器在安全层面的考虑,只允许本域下的接口进行交互。不同源的客户端请求不能够获取到对方的资源。

这是一种典型的防止CSRF攻击的手段。下面进行解释一下:

当你访问了淘宝,登上了账号并且准备买东西的时候。你不小心点到了其他网页去,这个时候你是已经在淘宝登录了你的账号,那么其他页面也是能够获得你的淘宝信息的。
假如有一些非法分子利用漏洞将你的信息全部盗走,然后模拟你的信息进行消费岂不是酿成了大祸。
因此,为了防止这种情况的发生,浏览器是禁止跨域进行资源访问的。

本域是指:协议、域名、端口都相同。三者有一个不同那么就是跨域。下面举个例子(图片转载链接文章已给出)。
什么是跨域问题?如何解决?

如果不解决跨域问题,那么就无法获取交互资源,出现报错。

如何解决跨域问题?

  1. Cors跨域中间件
  2. JsonP
  3. Nginx代理

Cros

什么是Cors呢?

Cors跨域资源分享(Cross Origin Resource Share),其实就是在浏览器中加上几个响应头,来让浏览器能够跨域访问资源。

这个响应头的设置就是:Access-Control-Allow-Origin: *,*就是允许所有的域名跨域。

预请求和Options

什么是预请求?为什么要使用预请求呢?

当一个请求跨域的目标不是简单请求的时候就会发起预请求,也就是 Options。如果没有预请求,假如一个毁灭性的Post请求发来,直接进行处理,处理完成之后返回的时候,浏览器虽然能告诉你你没有权限跨域访问资源又给你拦截起来了,但Post请求的目的已经达成了。

以下条件构成了简单请求:

  1. Method: 请求的方法是 GETPOSTHEAD
  2. Header: 请求头是 Content-Type (有限制)、Accept-LanguageContent-Language
  3. Content-Type: 请求类型是 application/x-www-form-urlencodedmultipart/form-datatext/plain

非简单请求一般需要开发者主动构造,在项目中常见的 Content-Type: application/jsonAuthorization: <token> 为典型的非简单请求。与之有关的三个字段如下:

  • Access-Control-Allow-Methods: 请求所允许的方法, 用于预请求 (preflight request) 中
  • Access-Control-Allow-Headers: 请求所允许的头,用于预请求 (preflight request) 中
  • Access-Control-Max-Age: 预请求的缓存时间

Cors对应的响应头都有哪些?

  • Access-Control-Allow-Origin:允许资源共享的域名 (*表示所有)
  • Access-Control-Allow-Credentials:请求是否可以带有Cookie
  • Access-Control-Allow-Methods:请求所允许的方法,用于预请求
  • Access-Control-Allow-Headers:请求所允许的头,用于预请求
  • Access-Control-Max-Age:预请求的缓存时间
  • Access-Control-Expose-Headers:哪些头可以在响应中暴露

简单请求过程

对于一个简单请求,浏览器不用发出预请求,直接发出CORS请求。其实就是在头部加一个Origin字段。

下面是一个例子:

GET /test HTTP/1.1
Origin: http://www.xxxxx.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

HTTP报文中,这个Origin就包括了本域内容:协议http + 域名 xxxx + 端口号 80。服务器根据这个Origin提供的信息来决定是否同意这次请求。

如果Origin指定的源不在许可范围之内,那么服务器就会返回一个正常的HTTP响应。如果浏览器发现这个响应不带有Access-Control-Allow-Origin这个首部字段,就会抛出一个XMLHttpRequest的错误。而状态码依旧是200。

如果Origin可以被通过,那么他将会收到一些额外的响应头

Access-Control-Allow-Origin: http://www.xxxxx.com
Access-Control-Allow-Credentials: true  - 是否携带cookie
Access-Control-Expose-Headers: FooBar - 允许暴露的首部

Content-Type: text/html; charset=utf-8

非简单请求过程

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

Cors如何设置多域名?

Cors的核心就在于Access-Control-Allow-Origin这个字段。只要设置了他基本就能解决跨域问题。而这个字段的值只有两种情况:

  1. *:所有域名
  2. xxx.com:特定域名

如果使用*,是无法携带Cookie的。

  1. 如果这个请求的域名携带了Origin,说明他是跨域请求,根据Origin设置相应的首部字段。
  2. 如果这个请求的域名没有携带Origin,说明他不是跨域请求,那就直接放行。

如何避免 CDN 为 PC 端缓存移动端页面

假如有两个域名访问同一个跨域资源,由于缓存问题,因此返回的Origin会不同。因此就需要用到Vary,代表不同的Origin缓存不同的资源。

中间件代码 -> 这里用的Gin框架

func Cors(c *gin.Context) {
   method := c.Request.Method
   header := c.Writer.Header()
   origin := c.Request.Header.Get("Origin")
   // 如果origin为 "" 说明不是跨域,null 不等于 ""
   if origin != "" {
      header.Add("Vary", "Origin")
      if !isAllowOrigin(origin) {
         // 该请求不允许
         c.Abort()
         return
      }
      // 说明这个请求是一个预请求
      if method == http.MethodOptions {
         reqMethod := header.Get("Access-Control-Request-Method")
         if reqMethod != "PUT" && reqMethod != "DELETE" {
            // 如果方法不对,不提供Access Origin字段。
            c.Abort()
            return
         }
         // 必须,接受指定域的请求,可以使用*不加以限制,但不安全
         //header.Set("Access-Control-Allow-Origin", "*")
         c.Header("Access-Control-Allow-Origin", origin)
         // 预请求最大缓存时间
         c.Header("Access-Control-Max-Age", "86400")
         // 必须,设置服务器支持的所有跨域请求的方法
         c.Header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
         // 服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段
         c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Token")
         // 可选,设置XMLHttpRequest的响应对象能拿到的额外字段
         c.Header("Access-Control-Expose-Headers", "Access-Control-Allow-Headers, Token")
         // 可选,是否允许后续请求携带认证信息Cookie,该值只能是true,不需要则不设置
         c.Header("Access-Control-Allow-Credentials", "true")
         c.AbortWithStatus(http.StatusNoContent)
         return
      }
       header.Set("Access-Control-Allow-Origin", origin)
       // 必须,设置服务器支持的所有跨域请求的方法
       c.Header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
       // 服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段
       c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Token")
       // 可选,设置XMLHttpRequest的响应对象能拿到的额外字段
       c.Header("Access-Control-Expose-Headers", "Access-Control-Allow-Headers, Token")
       // 可选,是否允许后续请求携带认证信息Cookie,该值只能是true,不需要则不设置
       c.Header("Access-Control-Allow-Credentials", "true")
       c.Next()
   }
}

效果:

什么是跨域问题?如何解决?

Nginx

直接利用反向代理进行处理

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

参考资料

浏览器跨域问题与服务器中的 CORS - 掘金 (juejin.cn)

跨域资源共享 CORS 详解 - 阮一峰的网络日志 (ruanyifeng.com)

不要再问我跨域的问题了 - 掘金 (juejin.cn)

什么是跨域?跨域的解决方法。 - 知乎 (zhihu.com)