ship icon indicating copy to clipboard operation
ship copied to clipboard

CORS 跨域中间件无法执行

Open xmx opened this issue 9 months ago • 5 comments

程序

package main

import (
	"net/http"

	"github.com/xgfone/ship/v5"
	"github.com/xgfone/ship/v5/middleware"
)

func main() {
	cors := middleware.CORS(&middleware.CORSConfig{
		AllowOrigins: []string{"*"},
		AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"},
		AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"},
	})
	r := ship.Default()
	r.Use(cors)
	r.Route("/ping").GET(func(c *ship.Context) error {
		return c.Text(http.StatusOK, "pong")
	})

	srv := &http.Server{
		Addr:    ":8080",
		Handler: r,
	}
	srv.ListenAndServe()
}

复现

$ curl -i -XOPTIONS http://127.0.0.1:8080/balabala -H "Origin: http://example.com" -H "Access-Control-Request-Method: GET"
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=UTF-8
Date: Wed, 23 Jul 2025 10:36:51 GMT
Content-Length: 9

Not Found

xmx avatar Jul 23 '25 10:07 xmx

简单请求 或 浏览器预检之后实际发起请求 的情况下,如果 req 的 Origin 不被允许,此时 CORS 中间件应该怎么做呢?

  1. 只添加 CORS 响应头,不阻止非法跨域请求继续传入业务层。

  2. 403 阻止非法跨域请求传入到业务层。

xmx avatar Jul 24 '25 08:07 xmx

中间件执行的时机有两个点:

  • 在路由前: 使用 Pre 方法注册中间件
  • 在路由后: 使用 Use 方法注册中间件

上面之所以返回 404 的原因,是使用了 Use 方法注册中间件,从而会先执行路由查找,只有当找到路由后,才使用执行 CORS 中间件。

所以,只需要把 Use 换成 Pre 即可,如下:

r.Pre(cors)

xgfone avatar Jul 24 '25 10:07 xgfone

如果不希望因为路由而返回404,而是需要在路由前先进行预处理,都需要使用 Pre 方法。

如果不关心是否返回 404,可一律使用 Use

xgfone avatar Jul 24 '25 11:07 xgfone

还有一个跨域的问题讨论一下:

简单请求 或 浏览器预检之后实际发起请求 的情况下,如果 req 的 Origin 不被允许,此时 CORS 中间件应该怎么做呢? Ship 的策略:只添加 CORS 响应头,不阻止非法跨域请求继续传入业务层。 Gin 的策略: 403 阻止非法跨域请求传入到业务层。

对与第一种只添加 Header 不阻止的宽松策略,并非 Ship 一个框架这么做,但我个人更倾向于 Gin CORS 的严格做法, 跨域本身就是为了阻止非法网站的请求。

宽松策略 CORS 没有匹配到 AllowOrigins,所以响应 Header Access-Control-Allow-Origin: 的 value 为空,在浏览器看来该请求被阻止跨域了,但是后端程序 cors 并未阻止该请求,实际业务程序已经被执行了。

我分别用 Gin 和 Ship 写一个等效的程序,用于演示差异:

  • https://a.example.com 后端允许跨域的网站 a
  • https://b.example.com 模拟跨域的网站 b

Gin CORS

程序

package main

import (
	"fmt"
	"net/http"

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

func main() {
	r := gin.Default()
	r.Use(cors.New(cors.Config{
		AllowOrigins: []string{"https://a.example.com"},
	}))

	r.GET("/ping", func(c *gin.Context) {
		fmt.Println("executed")
		c.String(http.StatusOK, "pong")
	})

	srv := &http.Server{
		Addr:    ":8080",
		Handler: r,
	}
	srv.ListenAndServe()
}

cURL 模拟跨域请求

$ curl -i -XGET http://localhost:8080/ping -H "Origin: https://b.example.com"
HTTP/1.1 403 Forbidden                                                                                                                                                                                                                    
Date: Fri, 25 Jul 2025 01:03:10 GMT
Content-Length: 0

# 返回 403,/ping 路由未执行。

Ship CORS

程序

package main

import (
	"fmt"
	"net/http"

	"github.com/xgfone/ship/v5"
	"github.com/xgfone/ship/v5/middleware"
)

func main() {
	cors := middleware.CORS(&middleware.CORSConfig{
		AllowOrigins: []string{"https://a.example.com"},
	})
	r := ship.Default()
	r.Pre(cors)
	r.Route("/ping").GET(func(c *ship.Context) error {
		fmt.Println("executed")
		return c.Text(http.StatusOK, "pong")
	})

	srv := &http.Server{
		Addr:    ":8080",
		Handler: r,
	}
	srv.ListenAndServe()
}

cURL 模拟跨域请求

$ curl -i -XGET http://localhost:8080/ping -H "Origin: https://b.example.com"
HTTP/1.1 200 OK                                                                                                                                                                                                                           
Access-Control-Allow-Origin: 
Content-Type: text/plain; charset=UTF-8
Vary: Origin
Date: Fri, 25 Jul 2025 01:10:12 GMT
Content-Length: 4

pong

# 返回 200,/ping 路由执行。

xmx avatar Jul 25 '25 01:07 xmx

宽松 或 严格,这两者策略在实践中都是可以的,具体取决于实际需求。或者,在实现 CORS 中间件时,两者都支持,但默认返回 204,如果需要返回 403 之类的,可以主动设置;注:这个前提是,CORS 中间件要支持。

另外,从 HTTP 标准来看,不应当返回除 204 之外的其它状态码;按 HTTP 标准,应当返回 204 状态码,同时不返回 Access-Control-Allow-Origin 等应答头。至于返回 Access-Control-Allow-Origin 等应答头、但其值为空,对于这种情况,是未定义的,一般浏览器会按未返回处理。

从这来看,Gin 的处理方式是不符合HTTP标准的。

xgfone avatar Jul 25 '25 02:07 xgfone