Gin+axios跨域问题详解


文章目录

  • Gin+axios跨域问题详解
  • 前言
  • 一 为什么会跨域
  • 1. 什么是源
  • 2. URL结构
  • 3. 同源不同源举🌰
  • 同源例子
  • 不同源例子
  • 4. 浏览器为什么需要同源策略
  • 5. 常规前端请求跨域
  • 二 前端解决方案
  • 三 后端解决方案
  • Http 协议CORS头
  • 四 设置跨域,依旧报错
  • 跨域回顾
  • 简单请求
  • 非简单请求
  • 前端axios 的处理
  • 后端修改代码,增加针对预检请求的处理



参考链接1

前言

跨域问题一直是前端的一大难题

标准的前端跨域报错:

Access to XMLHttpRequest at '…' from origin '…' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource

一 为什么会跨域

说到跨域不得不谈的就是浏览器的同源策略,跨域也是因为浏览器这个机制引起的,这个机制的存在还是在于安全。

1. 什么是源

Web内容的源由用于访问它的URL 的方案(协议),主机(域名)和端口定义。只有当方案,主机和端口都匹配时,两个对象具有相同的起源。

同源不同源一句话就可以判断:就是url中 scheme host port 都相同即为同源。

下面认识下url 结构中的这三个部分。

2. URL结构

URL 代表着是统一资源定位符(Uniform Resource Locator)。URL 无非就是一个给定的独特资源在 Web 上的地址。

URL有如下结构组成:

  • Schme 或者 Protocol

axios 如何实现跨域传递cookie axios跨域问题_gin

  • Domain Name 也叫做host域名

axios 如何实现跨域传递cookie axios跨域问题_javascript_02

  • port 端口号

axios 如何实现跨域传递cookie axios跨域问题_前端_03

  • Parameters参数

axios 如何实现跨域传递cookie axios跨域问题_跨域_04

  • Anchor 锚点,一般用于定位位置

axios 如何实现跨域传递cookie axios跨域问题_Access_05

3. 同源不同源举🌰

举一下同源不同源的例子,便于

同源例子

例子

原因

http://example.com/app1/index.html http://example.com/app2/index.html

相同的 scheme http 和host

http://Example.com:80 http://example.com

http 默认80端口所以同源

不同源例子

例子

原因

http://example.com/app1 https://example.com/app2

不同的协议

http://example.com http://myapp.example.com

不同的host

http://example.com http://example.com:8080

不同的端口

4. 浏览器为什么需要同源策略

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

5. 常规前端请求跨域

在没有前后端分离的时候,跨域问题往往是很少的。因为前后端都部署到一起。现在前后端分离不管vue /react 面临跨域请求的问题。

下面是引用官网描述的一张图来解释跨域:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ghiwNeq-1680414983802)(D:\25683\zhang\myBlog\firstblog\source\img\7000)]

跨源域资源共享(CORS)机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch)使用 CORS,以降低跨源 HTTP 请求所带来的风险。

二 前端解决方案

// 在http.js中引入axios
import Vue from "vue";
import axios from "axios"; // 引入axios

axios.defaults.timeout = 15000;
axios.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
axios.defaults.wheaders.post["Access-Control-Allow-Origin"] = "*";
axios.defaults.withCredentials=true;  // 允许携带cookie

三 后端解决方案

Http 协议CORS头

跨域其实也是http层面上可以解决的问题,后端解决也是比较简单的,也是项目常见的解决手法。

CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。

同源安全策略 默认阻止“跨域”获取资源。但是 CORS 给了web服务器这样的权限,即服务器可以选择,允许跨域请求访问到它们的资源。

  • Access-Control-Allow-Origin 指示请求的资源能共享给哪些域。
  • Access-Control-Allow-Credentials 指示当请求的凭证标记为 true 时,是否响应该请求。
  • Access-Control-Allow-Headers 用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。
  • Access-Control-Allow-Methods 指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源。
  • Access-Control-Expose-Headers 指示哪些 HTTP 头的名称能在响应中列出。
  • Access-Control-Max-Age 指示预请求的结果能被缓存多久。
  • Access-Control-Request-Headers 用于发起一个预请求,告知服务器正式请求会使用那些 HTTP 头。
  • Access-Control-Request-Method 用于发起一个预请求,告知服务器正式请求会使用哪一种 HTTP 请求方法。
  • Origin 指示获取资源的请求是从什么域发起的。

跨域设置可以使用官网推荐的插件

go get github.com/gin-contrib/cors

也可以自己写一个中间件

func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		if origin != "" {
			c.Header("Access-Control-Allow-Origin", "*")  // 可将 * 替换为指定的域名
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
			c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
			c.Header("Access-Control-Allow-Credentials", "true")
		}
		c.Next()
	}
}

当然上述的可以进行基本的请求操作。如果修改了header,使用浏览器进行 Get Post Header等请求的话,会默认进行OPTIONS的请求进行试探,要做下处理。

func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		if origin != "" {
			c.Header("Access-Control-Allow-Origin", "*")  // 可将 * 替换为指定的域名
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
			c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
			c.Header("Access-Control-Allow-Credentials", "true")
		}
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
		c.Next()
	}
}

注意的是Access-Control-Allow-Headers和Access-Control-Allow-Methods 值 如果设置不当也会造成跨域问题无法进行访问,可以去掉也可以进行加 * 操作

如果需要用到跨域携带cookie,session校验的话,“Access-Control-Allow-Origin”就不能设置为“*”,要设置为具体的源地址,否则会报安全错误

func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		if origin != "" {
			c.Header("Access-Control-Allow-Origin", "*")  // 可将 * 替换为指定的域名 加*即代表允许所有的跨域请求
			c.Header("Access-Control-Allow-Methods", "*") // 允许所有的请求方法类型
			//c.Header("Access-Control-Allow-Headers", "*") // 允许的请求头信息
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
			c.Header("Access-Control-Allow-Credentials", "true") // 允许校验携带的cookie,session。
		}
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
		c.Next()
	}

然后我们只需在gin中的相应的路由中加入中间件就行了

import (
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
)

func main() {
	app := gin.New()
	app.Use(cors.Default())

}

四 设置跨域,依旧报错

前后端均做跨域处理,但是却依旧请求失败

axios 如何实现跨域传递cookie axios跨域问题_Access_06

跨域回顾

浏览器的请求分为,简单请求,非简单请求。简单请求浏览器不会预检,而非简单请求会预检。

简单请求

同时满足下列三大条件,就属于简单请求,否则属于非简单请求

1. 请求方式只能是:GET、POST、HEAD

  1. HTTP请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID

3. Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain

对于简单请求,浏览器直接请求,会在请求头信息中,增加一个origin字段,来说明本次请求来自哪个源(协议+域名+端口)。服务器根据这个值,来决定是否同意该请求。服务器返回的响应会多几个和跨域相关的头信息字段

  • Access-Control-Allow-Origin:该字段是必须的,* 表示接受任意域名的请求,还可以指定域名
  • Access-Control-Allow-Credentials:该字段可选,是个布尔值,表示是否可以携带cookie,(注意:如果Access-Control-Allow-Origin字段设置*,此字段设为true无效)
  • Access-Control-Allow-Headers:表明服务器允许请求中携带字段 ,如Cache-Control、Content-Type、Expires等
  • Access-Control-Max-Age:有效时间,在有效时间内,浏览器无须为同一请求再次发起预检请求

非简单请求

非简单请求是对那种对服务器有特殊要求的请求,比如请求方式是PUT或者DELETE,或者Content-Type字段类型是application/json。都会在正式通信之前,增加一次HTTP请求,称之为预检。

浏览器会先询问服务器,当前网页所在域名是否在服务器的许可名单之中,服务器允许之后,浏览器会发出正式的XMLHttpRequest请求,否则会报错。(备注:之前碰到预检请求后端没有通过,就不会发正式请求)

针对预检请求的报错,我们可以采取以下两种处理方法

前端axios 的处理

经过不断尝试,发现在axios代码中如果去掉 axios.defaults.headers.post["Access-Control-Allow-Origin"] = "*"; 这行代码的话,请求可以了。但这样并不满足我们的需要,倘若前端不是我们自己开发的,后端如何处理才能解决这种问题呢?

后端修改代码,增加针对预检请求的处理
func Cors() gin.HandlerFunc {
   return func(c *gin.Context) {
      method := c.Request.Method
      origin := c.Request.Header.Get("Origin")
      if origin != "" {
         c.Header("Access-Control-Allow-Origin", "*")  
         c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
         c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization")
         c.Header("Access-Control-Allow-Credentials", "true")
         c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") 
      }
      if method == "OPTIONS" {
          /* 添加 */
         c.Header("Access-Control-Allow-Origin", "*")  
         c.Header("Access-Control-Allow-Methods", "OPTIONS")
         c.Header("Access-Control-Allow-Headers", "*")
          /* 添加 */
         c.AbortWithStatus(http.StatusNoContent)
      }
      c.Next()
   }
}

在请求是OPTIONS的时候,需要对请求头进行重新赋值,经过测试 c.Header("Access-Control-Allow-Headers", "*") 问题出现在这里,必须填* 否则都会出现上述问题

猜测可能axios或者浏览器内部的bug,前端不设置"Access-Control-Allow-Origin"就没有问题,或者前端设置,但是后端不对"Access-Control-Allow-Origin"做处理,具体不知道他们的交互逻辑是什么,也有人提出类似的问题,但是都没有说明原因,笔者这里也仅仅是猜测。