【计网】认识跨域,及其在go中通过注册CORS中间件解决跨域方案,go-zero、gin

一、跨域(CORS)是什么?

跨域,指的是浏览器出于安全限制,前端页面在访问不同源(协议、域名、端口任一不同)的后端接口时,会被浏览器拦截。
比如:

前端地址后端接口地址是否跨域
http://a.comhttp://a.com/api
http://a.comhttps://a.com/api是(协议不同)
http://a.comhttp://b.com/api是(域名不同)
http://a.com:8080http://a.com:8090/api是(端口不同)

浏览器会进行CORS检查。如果后端没有正确返回Access-Control-Allow-*的HTTP头信息,浏览器就会阻止前端访问。


二、浏览器的跨域流程

  1. 简单请求(Simple Request)

    • 请求方法限定:GET、POST、HEAD
    • Content-Type仅限于:application/x-www-form-urlencoded、multipart/form-data或text/plain
    • 没有对头部进行修改(如果修改,则是预检请求
  2. 预检请求(Preflight Request)

    • GET、POST、HEAD的请求方法
    • Content-Type为application/json等非简单值
    • 包含自定义头部字段

三、后端怎么处理跨域?

  • 常见跨域解决方案:CORS(跨源资源共享):W3C标准,服务端设置响应头实现

    • 处理跨域,就是要在HTTP响应中加上几个头:
  • Access-Control-Allow-Origin:允许访问的源(如*或https://example.com)

  • Access-Control-Allow-Methods:允许的HTTP方法(如GET, POST等)

  • Access-Control-Allow-Headers:允许的请求头字段

  • Access-Control-Allow-Credentials:是否允许发送Cookie(true/false)

  • Access-Control-Max-Age:预检请求缓存时间(秒)

CORS请求流程:

  1. 浏览器检测到跨域请求,自动添加Origin头
  2. 服务器检查Origin,决定是否允许并设置CORS响应头
  3. 对于预检请求,先发送OPTIONS请求确认权限
  4. 浏览器根据响应头决定是否允许实际请求

四、在 Go 后端怎么处理跨域?

这里讲两个框架:

1. 在 gin 框架中处理跨域

方法一:自己写中间件(不推荐)
func CorsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "*") // 允许所有域
        c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")

        // 处理预检请求
        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }

        c.Next()
    }
}

然后在main.go注册:

r := gin.Default()
r.Use(CorsMiddleware())

方法二:使用第三方库(推荐)

参考go文档:https://pkg.go.dev/github.com/gin-contrib/cors

用成熟的库,比如github.com/gin-contrib/cors

go get github.com/gin-contrib/cors

代码:

import "github.com/gin-contrib/cors"

// Cors 创建并返回一个gin.HandlerFunc处理程序,用于配置跨域资源共享(CORS)。
// 该函数通过调用cors.New并传递一个cors.Config结构体来实现,该结构体包含了CORS的配置信息。
// 主要目的是为了允许来自任何源的请求(AllowAllOrigins: true),并指定允许的HTTP请求头和方法,
// 同时支持凭证的跨域请求(AllowCredentials: true)。
func Cors() gin.HandlerFunc {
	return cors.New(cors.Config{
		// AllowAllOrigins: true 表示允许来自所有域的请求。
		AllowAllOrigins: true,
		// AllowHeaders 指定了允许的请求头,包括Origin, Content-Length, Content-Type和Authorization。
		AllowHeaders: []string{
			"Origin", "Content-Length", "Content-Type", "Authorization",
		},
		// AllowMethods 指定了允许的HTTP方法,包括GET, POST, PUT, DELETE, HEAD和OPTIONS。
		AllowMethods: []string{
			"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS",
		},
		// ExposeHeaders 指定了允许被访问的响应头,包括Content-Length, Access-Control-Allow-Origin,
		// Access-Control-Allow-Headers, Cache-Control, Content-Language和Content-Type。
		ExposeHeaders: []string{
			"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Cache-Control", "Content-Language", "Content-Type",
		},
		// AllowCredentials: true 表示允许跨域请求带上用户凭证。cookies
		AllowCredentials: true,
	})
}

main.go

r := gin.Default()
r.Use(middleware.Cors(), middleware.Auth()) // 这里可以抽象到routers.go 不过要注意中间件注册一定要在路由分组之前,因为这里的注册操作相当于给所有的URL设定cors规则,前后顺序不能颠倒

r.Use()是给之后所有注册的路由挂载这个中间件。
也就是说,如果你先注册了路由,再r.Use(),那些已经注册了的路由就不会受到新的中间件的影响。
简单讲:中间件只保护在它后面注册的路由。


2. 在 go-zero 框架中处理跨域

go-zero默认就带跨域处理,只要你在服务配置里面加上跨域设置。

比如etc/your-api.yaml配置文件中,写:

Name: your-api
Host: 0.0.0.0
Port: 8888
Cors: true

注意这个Cors: true,加了以后,go-zero框架自动帮你处理CORS,包括预检请求(OPTIONS)!

如果要自定义,比如允许指定源,可以在自定义中间件里处理。

也可以自己写中间件(不推荐,go-zero已经做了)
func CorsMiddleware() func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Access-Control-Allow-Origin", "*")
            w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
            w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

            if r.Method == "OPTIONS" {
                w.WriteHeader(http.StatusNoContent)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

注册:

server := rest.MustNewServer(c, rest.WithMiddlewares(
    []rest.Middleware{
        CorsMiddleware(),
    }...,
))

不过一般没必要,直接开Cors: true就行。


五、常见问题

  • OPTIONS请求后端返回404
    ➔ 需要后端显式处理OPTIONS方法,比如返回204 No Content。
  • 跨域时前端带了cookie但失败
    ➔ 需要Access-Control-Allow-Credentials: true,且Access-Control-Allow-Origin不能是*,必须指定具体域名。
  • 前端提示No ‘Access-Control-Allow-Origin’ header is present
    ➔ 后端没返回CORS头,检查中间件是否正确加载。

六、总结

  • 跨域是浏览器安全机制;
  • 处理跨域主要是设置HTTP响应头;
  • gin自己可以写中间件或用gin-contrib/cors
  • go-zero直接在配置里加Cors: true
  • OPTIONS预检请求一定要处理,否则请求直接失败。

七、针对go-zero中使用跨域使用方案拓展

  1. main.go方案
server := rest.MustNewServer(c.RestConf,
		rest.WithCustomCors(func(header http.Header) { // 1.这是自定义跨域逻辑
			var allowOrigin = "Access-Control-Allow-Origin"
			var allOrigins = "http://localhost:5173"
			var allowMethods = "Access-Control-Allow-Methods"
			var allowHeaders = "Access-Control-Allow-Headers"
			var exposeHeaders = "Access-Control-Expose-Headers"
			var methods = "GET, HEAD, POST, PATCH, PUT, DELETE, OPTIONS"
			var allowHeadersVal = "xxxx, Content-Type, Origin, X-CSRF-Token, Authorization, AccessToken, Token, Range"
			var exposeHeadersVal = "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers"
			var maxAgeHeader = "Access-Control-Max-Age"
			var maxAgeHeaderVal = "86400"
			header.Set(allowOrigin, allOrigins)
			header.Set(allowMethods, methods)
			header.Set(allowHeaders, allowHeadersVal)
			header.Set(exposeHeaders, exposeHeadersVal)
			header.Set(maxAgeHeader, maxAgeHeaderVal)
		}, func(w http.ResponseWriter) {

		}),
	)
	defer server.Stop()

	// 2.简单跨域
	//server := rest.MustNewServer(c.RestConf,
	//	rest.WithCors("http://localhost:5173"))

	// 3.自定义头 (预检请求)
	//server := rest.MustNewServer(c.RestConf,
	//	//顺序不能颠倒
	//	rest.WithCors("http://localhost:5173"),
	//	rest.WithCorsHeaders("xxxx"),
	//)


  1. 在 internal/middleware 目录下创建 cors.go 文件
package middleware

import (
	"net/http"
)

// Cors 中间件处理跨域请求
func Cors(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// 1. 设置允许访问的源
		// 生产环境应替换为具体的域名,如 "https://example.com"
		w.Header().Set("Access-Control-Allow-Origin", "*")
		
		// 2. 设置允许的HTTP方法
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		
		// 3. 设置允许的请求头
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		
		// 4. 设置是否允许发送Cookie等凭证信息
		// 如果设置为true,则Access-Control-Allow-Origin不能为*
		w.Header().Set("Access-Control-Allow-Credentials", "false")
		
		// 5. 设置预检请求缓存时间(秒)
		w.Header().Set("Access-Control-Max-Age", "86400")
		
		// 6. 如果是OPTIONS方法(预检请求),直接返回204
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		
		// 7. 继续处理后续中间件和路由
		next.ServeHTTP(w, r)
	})
}

在 main 函数中使用中间件

package main

import (
	"flag"
	"fmt"
	"xxx/internal/config"
	"xxx/internal/handler"
	"xxx/internal/middleware" // 引入中间件包
	"xxx/internal/svc"
	
	"github.com/zeromicro/go-zero/core/conf"
	"github.com/zeromicro/go-zero/rest"
)

var configFile = flag.String("f", "etc/xxx-api.yaml", "the config file")

func main() {
	flag.Parse()
	
	// 1. 加载配置文件
	var c config.Config
	conf.MustLoad(*configFile, &c)
	
	// 2. 创建服务上下文
	ctx := svc.NewServiceContext(c)
	
	// 3. 创建服务实例
	server := rest.MustNewServer(c.RestConf)
	defer server.Stop()
	
	// 4. 注册CORS中间件(关键步骤)
	server.Use(middleware.Cors)
	
	// 5. 注册路由处理器
	handler.RegisterHandlers(server, ctx)
	
	// 6. 打印启动信息并启动服务
	fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
	server.Start()
}

https://github.com/0voice

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值