Go猜想录
大道至简,悟者天成
如何解决 CORS 跨域问题?

跨域请求

协议、域名和端口任意一个不同,就是跨域请求。比如说当从前端地址 127.0.0.1:5500 发送请求到后端 127.0.0.1:8080 时就是一个跨域的请求,当不做额外的处理时,浏览器是无法正常处理的。它是浏览器的一种保护机制,主要是为了保证用户的安全,防止恶意网站窃取数据。

image.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CORS Test</title>
    <script>
        function makeRequest() {
            fetch('http://localhost:8080/data', {
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            .then(response => response.json())
            .then(data => {
                document.getElementById('response').textContent = JSON.stringify(data);
            })
            .catch(error => {
                document.getElementById('response').textContent = 'Error: ' + error;
            });
        }
    </script>
</head>
<body>
    <h1>CORS Test</h1>
    <button onclick="makeRequest()">Make Request</button>
    <pre id="response"></pre>
</body>
</html>

解决思路

从前端出发

可以在前端中加入一层代理,所有的请求都发送到 127.0.0.1:5500/api ,避免了不同源的问题。在代理收到请求后再转发给后端完成后续的步骤。

从后端出发

通过浏览器的 preflight 请求告诉浏览器允许接受来自127.0.0.1:5500 的请求。preflight 请求会使用 Options 方法,没有请求参数。

image.png

整个请求流程图如下:

preflight.png

下面是一个使用 gin 框架的中间件解决跨域问题的例子。这个中间件设置了 Access-Control 家族的几个 Header,这是解决跨越问题的关键。与此同时,业务请求的响应也会带上 Access-Control-Allow-Origin。具体需要填写什么样的配置项可以根据之前因为跨域问题问题导致失败的请求中得出。

package main

import (
	"net/http"
	"strings"
	"time"

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

func main() {
	r := gin.Default()

	r.Use(cors.New(cors.Config{
		AllowMethods: []string{"GET"},
		// 业务请求中可以带上的头
		AllowHeaders: []string{"Content-Type"},
		// 是否允许带上用户认证信息(如 cookie)
		AllowCredentials: true,
		// 哪些来源是允许的
		// AllowOrigins:     []string{"http://127.0.0.1:5500"},
		AllowOriginFunc: func(origin string) bool {
			if strings.HasPrefix(origin, "http://127.0.0.1") {
				//if strings.Contains(origin, "localhost") {
				return true
			}
			return strings.Contains(origin, "your_company.com")
		},
		MaxAge: 12 * time.Hour,
	}))

	r.GET("/data", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, `{"message": "Hello, world!"}`)
	})

	r.Run(":8080")
}

参考资料


知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。