CORS 预检神秘地因大猩猩/处理程序而失败

Posted

技术标签:

【中文标题】CORS 预检神秘地因大猩猩/处理程序而失败【英文标题】:CORS preflight mysteriously failing with gorilla/handlers 【发布时间】:2021-12-29 20:11:47 【问题描述】:

我正在通过 Heroku 为我的应用程序发布一个 Golang API。无法让我的 webapp(Flutter/Dart 堆栈)真正从我的 api 获得成功的响应。但是我 am 能够使用本地的 curl 命令获得成功的响应。我已经阅读了几篇关于更改 Go mux 服务器和添加正确标头的帖子,但这对我不起作用。我什至看到在我的 curl 请求期间返回了这些标头。真的可以使用一些帮助,因为这会减慢我的速度。

基本上这是我创建服务器的主要类

import (
    "api/preventative_care"
    "api/user"
    "github.com/gorilla/handlers"
    "github.com/gorilla/mux"
    "log"
    "net/http"
    "os"
)

func main() 
    log.SetFlags(log.LstdFlags | log.Llongfile)
    router := mux.NewRouter()

    // Where ORIGIN_ALLOWED is like `scheme://dns[:port]`, or `*` (insecure)

    headersOk := handlers.AllowedHeaders([]string"*")
    methodsOk := handlers.AllowedMethods([]string"GET", "HEAD", "POST", "PUT", "OPTIONS")
    originsOk := handlers.AllowedOrigins([]string"*")

    router.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) 
        log.Println("Up and running!")
    )
    router.HandleFunc("/api/login", user.LoginHandler).Methods("GET")
    router.HandleFunc("/api/recommendations", preventative_care.RecommendationHandler).Methods("GET")

    var port = os.Getenv("PORT")
    log.Printf("Starting application on port %s\n", port)

    //log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), router))
    log.Fatal(http.ListenAndServe(":" + os.Getenv("PORT"), handlers.CORS(originsOk, headersOk, methodsOk)(router)))


调用此 API 的 dart 代码如下所示:

    Map<String, String> headers = 
      "content-type": "application/json",
      "username": username,
      "password": password
    ;

    final response = await http.get(Uri.parse(uri), headers: headers);

我在 2 个独立的 Heroku Dynos 中同时托管 webapp 和 API。当我使用 curl 从本地访问 API 时,我看到如下:

$ > curl -iXGET https://my-app-api.herokuapp.com/api/login -H "username:hello" -H "password:pizza"

HTTP/1.1 200 OK
Server: Cowboy
Connection: keep-alive
Content-Type: application/json
Date: Thu, 18 Nov 2021 23:39:56 GMT
Content-Length: 160
Via: 1.1 vegur

我以为我应该看到那里添加了标题Access-Control-Allow-Origin: *,但我还没有获得 200 成功。但是,当我尝试使用我的 Webapp 从使用 Google Chrome 的登录屏幕点击 API 时,我看到了这个错误:

CORS 政策已阻止从源“https://my-app-staging.herokuapp.com”访问“https://my-app-api.herokuapp.com/api/login”处的 XMLHttpRequest:对预检请求的响应未通过访问控制检查:请求的资源上不存在“Access-Control-Allow-Origin”标头。

不知道 - 好像是 Chrome 正在删除标题或其他什么?

编辑:我也尝试使用 CURL 发送预检请求,我看到了正确的标头 - 但是它仍然显示 4XX 错误。

$ > curl -H "Access-Control-Request-Method: GET" -H "Origin: https://my-app-staging.herokuapp.com" --head https://my-app-api.herokuapp.com/api/login

HTTP/1.1 405 Method Not Allowed
Server: Cowboy
Connection: keep-alive
Access-Control-Allow-Origin: *
Date: Fri, 19 Nov 2021 00:51:52 GMT
Via: 1.1 vegur

所以现在我真的不确定

【问题讨论】:

【参考方案1】:

TL;DR

gorilla/handlers 不(还?)支持Access-Control-Allow-Headers 的通配符。您必须明确指定所有允许的标头。在你的情况下,而不是

handlers.AllowedHeaders([]string"*")

你应该有

handlers.AllowedHeaders([]string"content-type", "username", "password")

更多详情

Fetch standard 添加了对Access-Control-Allow-Headers 标头back in 2016 中通配符的支持(在非凭据请求的情况下)。大多数现代浏览器现在support this feature。

但是,gorilla/handlers 似乎还没有赶上规范。如果您检查the handlers.AllowedHeaders function 和the *cors.ServeHTTP method 的源代码,您会发现"*" 值没有特殊处理:它是按字面意思处理的。因此,预检请求(content-typeusernamepassword)提供的请求标头(detects a mismatch)和允许的标头(*,字面意思)和 responds with a 403 之间的 CORS 中间件 responds with a 403 没有甚至设置Access-Control-Allow-Origin 标头,从而导致访问控制检查失败。

【讨论】:

这确实为我解决了这个问题 - 这只是来自浏览器的一个非常令人困惑的错误,因为即使我已经指定了 Allowed-Origins * - 它向我抱怨 Access-Control-Allow-Origin: * 实际上它应该提到服务器不接受我尝试传递的标头的内容时丢失了! @alex 在这种情况下,gorilla/handlers 的实现可以说是造成您混乱的罪魁祸首。浏览器告诉你真相:对预检请求的响应不包含任何Access-Control-Allow-Origin 标头,因为gorilla/handlers 只有sets that header further down the *cors.ServeHTTP method,只有当所有请求标头都已成功检查您允许的标头时。 那里的分析真的很棒 - 甚至没有考虑过这种情况。谢谢

以上是关于CORS 预检神秘地因大猩猩/处理程序而失败的主要内容,如果未能解决你的问题,请参考以下文章

在启动中启用 CORS 失败并出现预检错误

预检失败时如何通过属性而不是通过 Application_BeginRequest 启用 CORS

CORS 预检在 Firefox 中失败,但在 Chrome for Apache CXF 中有效

IE 11 上 CORS 预检请求的奇怪问题失败

CORS 问题:预检响应具有无效的 HTTP 状态代码 403

如何在 SiteMinder 受保护的环境中处理 CORS 预检请求?