CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin'

huangapple go评论73阅读模式
英文:

CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin'

问题

我在后端使用了Golang的gin-gonic/gin web框架,在前端使用了React的axios。我已经尝试解决这个问题两天了,但是我仍然得到了下面相同的错误:

CORS策略:预检请求的响应未通过访问控制检查:所请求的资源上没有'Access-Control-Allow-Origin'头。

这个错误只发生在我尝试发送PATCH请求时,也就是需要预检OPTIONS请求的请求,但是对于不需要运行任何预检检查的GET和POST请求,一切都正常工作。

这是我的路由配置代码:

package main

import (
	"book_renting/api"
	"log"
	"net/http"

	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/contrib/cors"
	"github.com/gin-gonic/gin"
	_ "github.com/lib/pq"
)

func main() {

	router := gin.Default()
	store := cookie.NewStore([]byte("your-secret-key"))
	store.Options(sessions.Options{MaxAge: 60 * 60 * 24})

	router.Use(cors.Default())
	router.Use(sessions.Sessions("sessions", store))

	router.Use(func(c *gin.Context) {
		host := c.Request.Header.Get("Origin")
		c.Writer.Header().Set("Access-Control-Allow-Origin", host)
		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
		if c.Request.Method == "OPTIONS" {
			log.Println("Handling OPTIONS request")
			c.AbortWithStatus(http.StatusNoContent)
			return
		}
		log.Println("Executing CORS middleware")
		c.Next()
	})

	router.POST("/login", api.HandleLogin)
	router.GET("/logout", api.HandleLogout)
	router.POST("/register", api.HandleRegister)
	router.GET("/getCookie", api.GetCookieSession)

	router.GET("/books", api.GetBooksAPI)
	router.GET("/books/:id", api.BookByIdAPI)
	router.PATCH("/rent/:id", api.RentBookAPI)
	router.PATCH("/return/:id", api.ReturnBookAPI)
	router.Run("localhost:3000")
}

这是前端的代码:

import axios from 'axios';

const url = 'http://localhost:3000';

export const loginUser = async (credentials) => await axios.post(`${url}/login`, credentials, {withCredentials: true})
export const logoutUser = async () => await axios.get(`${url}/logout`, {withCredentials: true})
export const registerUser = () => axios.post(`${url}/register`)
export const fetchBooks = () => axios.get(`${url}/books`, { withCredentials: true })
export const fetchBookByID = (book_id) => axios.get(`${url}/books/${book_id}`, { withCredentials: true })
export const rentBook = (book_id) => axios.patch(`${url}/rent/${book_id}`, { withCredentials: true })
export const returnBook = (book_id) => axios.patch(`${url}/return/${book_id}`, { withCredentials: true })

我相当确定我正确设置了后端,应该返回所有必要的头部信息。

例如,对于GET请求,响应头部如下所示:

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Origin: http://localhost:3001
Content-Type: application/json; charset=utf-8
Date: Sat, 10 Jun 2023 22:12:11 GMT
Content-Length: 495

而对于PATCH请求尝试,我没有任何响应(不出所料),预检响应头部如下:

HTTP/1.1 200 OK
Date: Sat, 10 Jun 2023 22:12:12 GMT
Content-Length: 0

你有什么建议可能是问题所在?经过这两天的努力,我已经一筹莫展了。提前谢谢你!

我还尝试在if语句中再次设置头部:

c.Writer.Header().Set("Access-Control-Allow-Origin", host)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")

但是这一点也没有帮助。事实上,这个if语句在执行预检时并没有被执行,我从控制台知道服务器正在执行OPTIONS请求。

[GIN] 2023/06/11 - 00:12:13 | 200 |       7.708µs |       127.0.0.1 | OPTIONS  "/rent/2"

编辑:

这是发送PATCH请求的cURL命令(实际上是预检OPTIONS请求):

curl 'http://localhost:3000/return/2' \
  -X 'OPTIONS' \
  -H 'Accept: */*' \
  -H 'Accept-Language: en-US,en;q=0.9,pl-PL;q=0.8,pl;q=0.7' \
  -H 'Access-Control-Request-Headers: content-type' \
  -H 'Access-Control-Request-Method: PATCH' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Origin: http://localhost:3001' \
  -H 'Pragma: no-cache' \
  -H 'Referer: http://localhost:3001/' \
  -H 'Sec-Fetch-Dest: empty' \
  -H 'Sec-Fetch-Mode: cors' \
  -H 'Sec-Fetch-Site: same-site' \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \
  --compressed

这是对该请求的响应:

HTTP/1.1 200 OK
Date: Sun, 11 Jun 2023 01:22:57 GMT
Content-Length: 0
英文:

I am using Golang with gin-gonic/gin web framework in my backend and with React axios in my frontend. I tried to solve it for two days already, but I still get the same error below:

CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

This error happens only when I try to send the PATCH request, so the one which requires the preflight OPTIONS request, but everything works as expected for GET and POST, which do not run any preflight checks.

Here is the code for my router configuration:

package main
import (
"book_renting/api"
"log"
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/contrib/cors"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
func main() {
router := gin.Default()
store := cookie.NewStore([]byte("your-secret-key"))
store.Options(sessions.Options{MaxAge: 60 * 60 * 24})
router.Use(cors.Default())
router.Use(sessions.Sessions("sessions", store))
router.Use(func(c *gin.Context) {
host := c.Request.Header.Get("Origin")
c.Writer.Header().Set("Access-Control-Allow-Origin", host)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
if c.Request.Method == "OPTIONS" {
log.Println("Handling OPTIONS request")
c.AbortWithStatus(http.StatusNoContent)
return
}
log.Println("Executing CORS middleware")
c.Next()
})
router.POST("/login", api.HandleLogin)
router.GET("/logout", api.HandleLogout)
router.POST("/register", api.HandleRegister)
router.GET("/getCookie", api.GetCookieSession)
router.GET("/books", api.GetBooksAPI)
router.GET("/books/:id", api.BookByIdAPI)
router.PATCH("/rent/:id", api.RentBookAPI)
router.PATCH("/return/:id", api.ReturnBookAPI)
router.Run("localhost:3000")
}

And here is the frontend side:

import axios from 'axios'
const url = 'http://localhost:3000'
export const loginUser = async (credentials) => await axios.post(`${url}/login`, credentials, {withCredentials: true})
export const logoutUser = async () => await axios.get(`${url}/logout`, {withCredentials: true})
export const registerUser = () => axios.post(`${url}/register`)
export const fetchBooks = () => axios.get(`${url}/books`, { withCredentials: true })
export const fetchBookByID = (book_id) => axios.get(`${url}/books/${book_id}`, { withCredentials: true })
export const rentBook = (book_id) => axios.patch(`${url}/rent/${book_id}`, { withCredentials: true })
export const returnBook = (book_id) => axios.patch(`${url}/return/${book_id}`, { withCredentials: true })

I am quite certain that I set up the backend side properly, that it should return all necessary headers.

For example for the GET request the response headers look like this:

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Origin: http://localhost:3001
Content-Type: application/json; charset=utf-8
Date: Sat, 10 Jun 2023 22:12:11 GMT
Content-Length: 495

While for the PATCH request attempt I do not have any response (not surprisingly) and the preflight response headers are:

HTTP/1.1 200 OK
Date: Sat, 10 Jun 2023 22:12:12 GMT
Content-Length: 0

Do you have any suggestions what could be the issue? After these two days I am already clueless. Thank you in advance!

I also tried to put headers:

c.Writer.Header().Set("Access-Control-Allow-Origin", host)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")

...again in the if statement:

if c.Request.Method == "OPTIONS" {
log.Println("Handling OPTIONS request")
c.AbortWithStatus(http.StatusNoContent)
return
}

But it didn't help at all. In fact, this if statement is not executed when the preflight is being performed and I know from the console that the server is executing the OPTIONS request.

[GIN] 2023/06/11 - 00:12:13 | 200 |       7.708µs |       127.0.0.1 | OPTIONS  "/rent/2"

EDIT:

Here is the cURL command sending the PATCH request (so in fact here is the preflight OPTIONS request):

curl 'http://localhost:3000/return/2' \
-X 'OPTIONS' \
-H 'Accept: */*' \
-H 'Accept-Language: en-US,en;q=0.9,pl-PL;q=0.8,pl;q=0.7' \
-H 'Access-Control-Request-Headers: content-type' \
-H 'Access-Control-Request-Method: PATCH' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-H 'Origin: http://localhost:3001' \
-H 'Pragma: no-cache' \
-H 'Referer: http://localhost:3001/' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-site' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \
--compressed

And the response to this request:

HTTP/1.1 200 OK
Date: Sun, 11 Jun 2023 01:22:57 GMT
Content-Length: 0

答案1

得分: 1

原来你正在使用已弃用的包github.com/gin-gonic/contrib/cors。你应该使用github.com/gin-contrib/cors代替。以下是使用github.com/gin-contrib/cors的演示配置:

package main

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

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

	config := cors.DefaultConfig()
	config.AddAllowHeaders("Authorization")
	config.AllowCredentials = true
	config.AllowAllOrigins = false
	// 我认为你应该只允许白名单中的有限来源:
	//	config.AllowAllOrigins = []string{"xxxx", "xxxx"}
	config.AllowOriginFunc = func(origin string) bool {
		return true
	}
	router.Use(cors.New(config))

	store := cookie.NewStore([]byte("your-secret-key"))
	store.Options(sessions.Options{MaxAge: 60 * 60 * 24})
	router.Use(sessions.Sessions("sessions", store))

	// 下面是路由

	router.Run("localhost:3000")
}

> 由于某种原因,PATCH请求头缺少"Cookie"头,尽管我使用了withCredentials参数。

axios.patch(`${url}/rent/${book_id}`, { withCredentials: true })

这里{ withCredentials: true }被当作数据处理,而没有配置项。如果你没有要发送到服务器的数据,应该这样写:

axios.patch(`${url}/rent/${book_id}`, null, { withCredentials: true })
英文:

It turns out that you're using the deprecated package github.com/gin-gonic/contrib/cors. You should use github.com/gin-contrib/cors instead. Here is a demo configuration to use github.com/gin-contrib/cors:

package main

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

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

	config := cors.DefaultConfig()
	config.AddAllowHeaders("Authorization")
	config.AllowCredentials = true
	config.AllowAllOrigins = false
	// I think you should whitelist a limited origins instead:
	//	config.AllowAllOrigins = []{"xxxx", "xxxx"}
	config.AllowOriginFunc = func(origin string) bool {
		return true
	}
	router.Use(cors.New(config))

	store := cookie.NewStore([]byte("your-secret-key"))
	store.Options(sessions.Options{MaxAge: 60 * 60 * 24})
	router.Use(sessions.Sessions("sessions", store))

	// routes below

	router.Run("localhost:3000")
}

> The PATCH request header for some reason lacks the "Cookie" header, despite the fact that I use the withCredentials parameter.

axios.patch(`${url}/rent/${book_id}`, { withCredentials: true })

Here { withCredentials: true } is treated as the data, and there is not config. If you don't have data to send to the server, you should write it like this:

axios.patch(`${url}/rent/${book_id}`, null, { withCredentials: true })

答案2

得分: 0

尽管在这种情况下似乎应该返回204响应,但规范有些不明确,所以为了使CORS在所有浏览器中正常工作,您可能需要返回200作为选项请求的响应。

英文:

While it seems that a 204 response would be appropriate here, the specification is a bit unclear, so you may need to return a 200 for the options request to make CORS work in all browsers.

答案3

得分: 0

你在中间件中所做的工作似乎足够了,没有问题。因为CORS适用于其他方法。

你是否处理了这个特定的 "api.ReturnBookAPI" 处理程序中的所有错误?

如果请求出现错误,并且无法正确返回gin响应,那么CORS标头将不会反映在响应标头中。

你的响应中的 Content-Length: 0 字段显示没有任何响应体

检查gin服务日志输出,并确保打印和检查了所有潜在的错误。

英文:

what you have done in the middleware seems enough and with no issues. since the CORS are applying to other methods.

have you handled all the errors in this specific "api.ReturnBookAPI" handler?

if a request got error and it wouldn't return the gin response appropriately, the CORS headers wouldn't be reflected in the response headers.

Content-Length: 0 this field in your res shows there are no response body at all

check the gin service log output, and ensure you have printed and checked all the potential errors.

huangapple
  • 本文由 发表于 2023年6月11日 06:40:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76448408.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定