Golang——通过实例了解并解决CORS跨域问题

简介: Golang——通过实例了解并解决CORS跨域问题

跨源资源共享

实例

运行在http://localhost:8082端口的前端服务器express和运行在http://localhost:8080端口的后端服务器golang net/http。前端的javaScript代码使用fetch()函数发起一个到http://localhost:8080/api/students的请求。

后端代码:

/server/main.go

import (
  "encoding/json"
  "fmt"
  "net/http"
  _ "github.com/go-sql-driver/mysql"
  "github.com/jmoiron/sqlx"
)
var db *sqlx.DB
type Student struct {
  ID   int64
  Name string
  Sex  string
  Age  int64
}
// 连接数据库
func init(){
    dns := "user:pwd@tcp(localhost:3306)/db_name?charset=utf8&parseTime=True&loc=Local"
  db, err = sqlx.Open("mysql", dns)
  if err != nil {
        fmt.Println(err)
        return
  }
  db.SetMaxOpenConns(2000)
  db.SetMaxIdleConns(1000)
}
// 获取所有学生信息(数据自己事先插入)
func GetAllStudent() []Student {
  sqlStr := "SELECT * FROM student"
  students := make([]Student, 0)
  rows, _ := db.Query(sqlStr)
  student := Student{}
  for rows.Next() {
    rows.Scan(&student.ID, &student.Name, &student.Sex, &student.Age)
    students = append(students, student)
  }
  defer rows.Close()
  return students
}
func GetAllStudentInfo(w http.ResponseWriter, r *http.Request) {
  students := GetAllStudent()
  resp := make(map[string]interface{})
  resp["msg"] = "成功"
  resp["code"] = "200"
  resp["data"] = students
  jsonResp, err := json.Marshal(resp)
  if err != nil {
    fmt.Println(err)
    return
  }
  w.Write(jsonResp)
}
func main() {
  http.HandleFunc("/api/students", GetAllStudentInfo)
  http.ListenAndServe(":8080", nil)
}

前端代码:

没有下载express的在/client目录执行:

npm install express --save-dev

/client/main.js

import express from 'express'
// 返回了一个服务器对象
const app = express()
// express.static(): 指定静态资源所在目录
app.use(express.static('./'))
app.listen(8082)

启动前端服务器:node main.js

/client/students.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>学生信息</title>
</head>
<body>
    <script>
        // 以同步方式获取响应
        async function getStudents() {
            const promiseResp = await fetch("http://localhost:8080/api/students")
            const resp = await promiseResp.json()
            console.log(resp)
        }
        getStudents()
    </script>
</body>
</html>

访问:http://localhost:8082/students.html

可以看到控制台里打印的并不是我们预期的后端给的数据,这是为什么呢?

首先,我们要知道照成这个错误的原因是什么,我们先看整个请求相应的流程是什么样的:

问题清楚了,那么如何解决呢?

解决方法1:

交给后端来做

其实我们发送fetch请求的时候,如果你的发送者和你要访问的资源不同源的情况下,就会在请求中包含一个特殊的头Origin,这个头代表着发送者的源是谁,比如说我们这个例子里,发送者是students.html,它的源是localhost:8082,所以当students.html发一个请求给后端服务器的时候,就会携带Origin:http://localhost:8082,告诉后端服务器发送者来自于哪里(通俗来说就是,我是8082端口的人,我来要你8080端口的资源,你给不给吧),那么对于后端服务器这边来讲就要对这个请求做出选择了,如果允许8082访问自己的资源,就需要在响应里包含一个Access-Control-Allow-Origin头,如果不允许8082访问自己的资源,不加这个头即可。如果这个头的内容是Access-Control-Allow-Origin:http://localhost:8082,意思就是后端服务器这个响应只能给http://localhost:8082端口使用,别人不让用,如果这个头的内容是Access-Control-Allow-Origin:*,意思就是这个响应谁都可以用。

我们打开F12,查看网络:

可以看到请求头里是有一个上面说的Origin头,上面说了,只要他fetch发生了跨域,就会有一个Origin头。

我们来看服务器的响应,可以看到并没有做处理,服务器响应这边并没有Access-Control-Allow-Origin头,所以浏览器拿到这个响应之后报错了,发现后端服务器那边没有允许。

说到这里,想必也知道如何处理了,在后端服务器的响应里加入这个头,允许http://localhost:8082使用这个响应即可:

w.Header().Set("Access-Control-Allow-Origin", "http://localhost:8082")

重新启动后端服务器,刷新页面可以看到浏览器将响应给了students.html页面,此时在查看响应表头,就会发现有了Access-Control-Allow-Origin头:

解决方法2:

交给前端来做

除了上面说的解决方法1,还可以通过代理解决:

这次我们在前端服务器里加入了一个代理的插件,此时前端服务器就和浏览器有一个约定,原本浏览器有一部分请求发送给8082,有一部分发送给8080,这个新的约定就是说:

以后浏览器的所有请求都发给前端服务器8082,所以发请求就应该是向http://localhost:8082/api/students发了,可是8082并有这个数据呀,8080才有, 所以这个请求就要发给前端服务器的代理,然后由代理间接的再找8080请求数据,然后8080会把数据响应给8082,再由8082间接的返回给浏览器里的students.html

这时候我们来看,对于浏览器来说,有没有发生跨域问题?

并没有,因为它是向同源的8082发的请求,是没有Origin头的。

至于代理发的请求,它是通过JavaScript的API发请求,接响应的,是没有什么同源策略、跨域问题。

跨域和同源都是浏览器的特殊行为。

如何区分我这个请求到底是走8082还是走8080呢?

一般是通过请求的前缀路径来区分的,比如说需要找后端要的数据,咱们都给他加一个特殊的前缀/api/,这样只要你的请求是以/api/开头的,这些请求都是走代理,然后经过代理间接找后端请求的,如果你的请求没有加/api/这个前缀,这些请求就访问8082自己,找到这些网页资源。

看下面代码就明白了:

如果没有下载http-proxy-middleware,在/client目录执行:

npm install http-proxy-middleware --save-dev

/client/students.html

// 修改请求地址,由8080改为8082
const promiseResp = await fetch("http://localhost:8082/api/students")

/client/main.js

import express from 'express'
import { createProxyMiddleware } from 'http-proxy-middleware'
// 返回了一个服务器对象
const app = express()
// express.static(): 指定静态资源所在目录
app.use(express.static('./'))
// 添加代理,凡是以/api为前缀的,都代理到 http://localhost:8080
app.use('/api', createProxyMiddleware({
    target: "http://localhost:8080",
    changeOrigin: true
}
));
app.listen(8082)

重启前端服务:node main.js

再次访问http://localhost:8082/student.html

可以看到响应被获取到了:

查看网络,请求头里是没有Origin头的:

总结:

只要协议、主机、端口之一不同,就是不同源,比如:

http://localhost:8080/a和https://localhost:8080/b就不同源。

同源检查是浏览器的行为,而且只针对fetch、XMLHttpRequest请求

如果是其他客户端,例如golang net/http client、postman,他们是不做同源检查的。

通过表单提交,浏览器直接输入url地址这些方式发送的请求,也不会做同源检查。


  1. 更多相关知识请参考:
    MDNhttps://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
  2. 补充:

Golang解决跨域的完整代码:

上面给的解决方法1,只是针对例子的简陋的版本,真正go通过CORS解决跨域问题的完整代码:

gin中间件:

func Cors(context *gin.Context) {
  method := context.Request.Method
   // 1. [必须]接受指定域的请求,可以使用*不加以限制,但不安全
  //context.Header("Access-Control-Allow-Origin", "*")
  context.Header("Access-Control-Allow-Origin", context.GetHeader("Origin"))
  fmt.Println(context.GetHeader("Origin"))
  // 2. [必须]设置服务器支持的所有跨域请求的方法
  context.Header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
   // 3. [可选]服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段
  context.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Token")
   // 4. [可选]设置XMLHttpRequest的响应对象能拿到的额外字段
  context.Header("Access-Control-Expose-Headers", "Access-Control-Allow-Headers, Token")
  // 5. [可选]是否允许后续请求携带认证信息Cookir,该值只能是true,不需要则不设置
  context.Header("Access-Control-Allow-Credentials", "true")
  // 6. 放行所有OPTIONS方法
  if method == "OPTIONS" {
    context.AbortWithStatus(http.StatusNoContent)
    return
  }
  context.Next()
}

原生HTTP中间件:

func corsMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // 1. [必须]接受指定域的请求,可以使用*不加以限制,但不安全
    // w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
    // 2. [必须]设置服务器支持的所有跨域请求的方法
    w.Header().Set("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE,OPTIONS")
    // 3. [可选]服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Content-Length,Token")
    // 4. [可选]设置XMLHttpRequest的响应对象能拿到的额外字段
    w.Header().Set("Access-Control-Expose-Headers", "Access-Control-Allow-Headers,Token")
    // 5. [可选]是否允许后续请求携带认证信息Cookir,该值只能是true,不需要则不设置
    w.Header().Set("Access-Control-Allow-Credentials", "true")
    next.ServeHTTP(w, r)
  })
}
func main() {
  http.Handle("/api/test", corsMiddleware(http.HandlerFunc(test)))
  http.ListenAndServe(":8080", nil)
}
相关文章
|
3月前
|
JSON 安全 前端开发
浅析CORS跨域漏洞与JSONP劫持
浅析CORS跨域漏洞与JSONP劫持
111 3
|
28天前
|
开发框架 中间件 Java
如何处理跨域资源共享(CORS)的 OPTIONS 请求?
处理 CORS 的 OPTIONS 请求的关键是正确设置响应头,以告知浏览器是否允许跨域请求以及允许的具体条件。根据所使用的服务器端技术和框架,可以选择相应的方法来实现对 OPTIONS 请求的处理,从而确保跨域资源共享的正常进行。
|
28天前
|
JavaScript 前端开发 API
跨域资源共享(CORS)的工作原理是什么?
跨域资源共享(CORS)通过浏览器和服务器之间的这种交互机制,在保证安全性的前提下,实现了跨域资源的访问,使得不同源的网页能够合法地获取和共享服务器端的资源,为现代Web应用的开发提供了更大的灵活性和扩展性。
|
2月前
|
JSON 前端开发 安全
CORS 是什么?它是如何解决跨域问题的?
【10月更文挑战第20天】CORS 是一种通过服务器端配置和浏览器端协商来解决跨域问题的机制。它为跨域资源共享提供了一种规范和有效的方法,使得前端开发人员能够更加方便地进行跨域数据交互。
|
2月前
|
缓存 前端开发 应用服务中间件
CORS跨域+Nginx配置、Apache配置
CORS跨域+Nginx配置、Apache配置
193 7
|
3月前
|
安全
CORS 跨域资源共享的实现原理
CORS 跨域资源共享的实现原理
|
4月前
|
Web App开发 JSON 数据格式
【Azure Developer】浏览器查看本地数据文件时遇见跨域问题(CORS)
【Azure Developer】浏览器查看本地数据文件时遇见跨域问题(CORS)
【Azure Developer】浏览器查看本地数据文件时遇见跨域问题(CORS)
|
4月前
|
API
【Azure Function】Function本地调试时遇见跨域问题(blocked by CORS policy)
【Azure Function】Function本地调试时遇见跨域问题(blocked by CORS policy)
【Azure Function】Function本地调试时遇见跨域问题(blocked by CORS policy)
|
4月前
|
安全 前端开发 Java
Web端系统开发解决跨域问题——以Java SpringBoot框架配置Cors为例
在Web安全上下文中,源(Origin)是指一个URL的协议、域名和端口号的组合。这三个部分共同定义了资源的来源,浏览器会根据这些信息来判断两个资源是否属于同一源。例如,https://www.example.com:443和http://www.example.com虽然域名相同,但由于协议和端口号不同,它们被视为不同的源。同源(Same-Origin)是指两个URL的协议、域名和端口号完全相同。只有当这些条件都满足时,浏览器才认为这两个资源来自同一源,从而允许它们之间的交互操作。
Web端系统开发解决跨域问题——以Java SpringBoot框架配置Cors为例
|
5月前
|
安全
CORS 跨域资源共享的实现原理
CORS 跨域资源共享的实现原理