卓越飞翔博客卓越飞翔博客

卓越飞翔 - 您值得收藏的技术分享站
技术文章33969本站已运行390

如何解决 Go 聊天应用程序中 cookie 未在本地主机端口之间传输的问题?

如何解决 go 聊天应用程序中 cookie 未在本地主机端口之间传输的问题?

问题内容

我在聊天应用程序中遇到基于 cookie 的令牌身份验证问题。我正在使用带有标准网络库的 go 后端来将令牌添加到响应 cookie。当用户通过密码验证(通过 post 到身份验证服务器上的 /login 路径)时,响应 cookie 应包含用于生成 api 令牌的访问令牌和用于重新生成访问令牌的刷新令牌。

这是一个标记文件,其中包含我的开发环境中应用程序服务的结构。每个服务器都在本地主机上使用 go net/http 在顺序端口上运行(不显示不相关的服务)。

auth_server (
    dependencies []
    url (scheme "http" domain "localhost" port "8081")
    listenaddress ":8081"
    endpoints (
        /jwtkeypub (
            methods [get]
        )
        /register (
            methods [post]
        )
        /logout (
            methods [post]
        )
        /login (
            methods [post]
        )
        /apitokens (
            methods [get]
        )
        /accesstokens (
            methods [get]
        )
    )
    jwtinfo (
        issuername "auth_server"
        audiencename "auth_server"
    )
)

message_server (
    dependencies [auth_server]
    url (scheme "http" domain "localhost" port "8083")
    listenaddress ":8083"
    endpoints (
        /ws (
            methods [get]
        )
    )
    jwtinfo (
        audiencename "message_server"
    )
)

static (
    dependencies [auth_server, message_server]
    url (scheme "http" domain "localhost" port "8080")
    listenaddress ":8080"
)

这是在登录时设置 cookie 的代码。密码检查后会发生这种情况

// set a new refresh token
    refreshtoken := s.jwtissuer.stringifyjwt(
        s.jwtissuer.minttoken(userid, s.jwtissuer.name, refreshtokenttl),
    )
    kit.sethttponlycookie(w, "refreshtoken", refreshtoken, int(refreshtokenttl.seconds()))

    // set a new access token
    accesstoken := s.jwtissuer.stringifyjwt(
        s.jwtissuer.minttoken(userid, s.jwtaudience.name, accesstokenttl),
    )
    kit.sethttponlycookie(w, "accesstoken", accesstoken, int(accesstokenttl.seconds()))
}
func sethttponlycookie(w http.responsewriter, name, value string, maxage int) {
    http.setcookie(w, &http.cookie{
        name:     name,
        value:    value,
        httponly: true,
        maxage:   maxage,
    })
}

以下是当用户请求 api 令牌时我如何访问 cookie。如果返回错误,处理程序将调用 gettokenfromcookie() 函数并以 401 进行响应。这种情况下的错误是“http:命名的cookie不存在”

func gethttpcookie(r *http.request, name string) (*http.cookie, error) {
    return r.cookie(name)
}

func gettokenfromcookie(r *http.request, name string) (jwt.jwt, error) {
    tokencookie, err := gethttpcookie(r, name)
    if err != nil {
        // debug
        log.println(err)
        return jwt.jwt{}, err
    }

    return jwt.fromstring(tokencookie.value)
}

来自登录端点的 200 响应后,页面重定向到主应用程序页面。在此页面上,向身份验证服务器发出请求以接收用于连接实时聊天消息服务器的 api 令牌。从auth服务器上的日志输出可以看到,请求中没有收到访问令牌cookie,因此请求返回401代码。

2023/05/19 02:33:57 get [/jwtkeypub] - 200
2023/05/19 02:33:57 get [/jwtkeypub] - 200
2023/05/19 02:34:23 post [/login] - 200
2023/05/19 02:34:23 http: named cookie not present
{{ } {    } []} http: named cookie not present
2023/05/19 02:34:23 get [/apitokens?aud=msgservice] - 401

我相信问题在于我使用的是 localhost,并且浏览器不会将 cookie 从 locahost:8080 传输到 localhost:8081。我正计划实现某种模拟身份验证,绕过读取开发环境的 cookie 来解决这个问题,但我不确定这是否真的是我的问题的原因。只是想再看一下,看看我是否可以让它工作而不需要这样做。

更新:我已经查看了开发工具中的网络选项卡: 图像显示登录后的响应返回了 cookie,但它们随后不会发送到端口 8081 上的身份验证服务器。在获得登录的 200 响应后,我还查看了 cookie 存储,即使在之后也没有 cookie在响应中接收它们。我正在使用 firefox 的私人模式来访问该网站。请注意,即使我在 go 代码中设置了 maxage,cookie 也不包含 maxage,这似乎是一个问题。

更新:这是登录后的 har 文件。您可以看到响应有 max-age,但之后它不会显示在 cookies 选项卡中。

{
  "log": {
    "version": "1.2",
    "creator": {
      "name": "Firefox",
      "version": "113.0.1"
    },
    "browser": {
      "name": "Firefox",
      "version": "113.0.1"
    },
    "pages": [
      {
        "startedDateTime": "2023-05-19T12:16:37.081-04:00",
        "id": "page_1",
        "title": "Login Page",
        "pageTimings": {
          "onContentLoad": -8105,
          "onLoad": -8077
        }
      }
    ],
    "entries": [
      {
        "pageref": "page_1",
        "startedDateTime": "2023-05-19T12:16:37.081-04:00",
        "request": {
          "bodySize": 31,
          "method": "POST",
          "url": "http://0.0.0.0:8081/login",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "Host",
              "value": "0.0.0.0:8081"
            },
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0"
            },
            {
              "name": "Accept",
              "value": "*/*"
            },
            {
              "name": "Accept-Language",
              "value": "en-US,en;q=0.5"
            },
            {
              "name": "Accept-Encoding",
              "value": "gzip, deflate"
            },
            {
              "name": "Referer",
              "value": "http://localhost:8080/"
            },
            {
              "name": "Content-Type",
              "value": "text/plain;charset=UTF-8"
            },
            {
              "name": "Content-Length",
              "value": "31"
            },
            {
              "name": "Origin",
              "value": "http://localhost:8080"
            },
            {
              "name": "DNT",
              "value": "1"
            },
            {
              "name": "Connection",
              "value": "keep-alive"
            }
          ],
          "cookies": [],
          "queryString": [],
          "headersSize": 370,
          "postData": {
            "mimeType": "text/plain;charset=UTF-8",
            "params": [],
            "text": "{"username":"a","password":"a"}"
          }
        },
        "response": {
          "status": 200,
          "statusText": "OK",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "Access-Control-Allow-Origin",
              "value": "*"
            },
            {
              "name": "Set-Cookie",
              "value": "refreshToken=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQMmY2RHg1RWxlYTF5THBUaVpEejBaS3Z1dk1FUkFPZEtBVGkwNDZSc2JNPSIsImF1ZCI6InN0ZWVsaXgiLCJpc3MiOiJzdGVlbGl4IiwiZXhwIjoiMTY4NTExNzc5NyIsImp0aSI6IjIwMUQzODZDNTRBQzlEOUMwRjdCODFBMDVDNDlFQTE1In0.SbxFgEAtZbh0zS-SXZmrVW9iLk-cFz6HcDMU0FHNl-K9BwCeb_boc5igEgImMSYK-NBVQZh1km7YknE-jkBWyF0rIYjSnTzjNUHHwMnn0jE1N-dtEfNRnF1OT0R2bxPSz8gmhtJ3B839xa-jh9uMPMkXEB8BYtABgPH1FqBdijHPUtRVKq6C3ulVleurp2eyF8EHpGLc9rr5wBYSFBk0HQ3FNjjUxfRQLDnzl2xYovoQ2em4grExnkdACxCSpXNtF5bQ7lCnEZyf7-CehrRNwZCpteGKj5ux_wrX_nxma3OEWwrlatML_j-e420TM1tub0C9Ymyt0bMugHw8vaiOGA; Max-Age=604800; HttpOnly"
            },
            {
              "name": "Set-Cookie",
              "value": "accessToken=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQMmY2RHg1RWxlYTF5THBUaVpEejBaS3Z1dk1FUkFPZEtBVGkwNDZSc2JNPSIsImF1ZCI6InN0ZWVsaXgiLCJpc3MiOiJzdGVlbGl4IiwiZXhwIjoiMTY4NDUxNDE5NyIsImp0aSI6IjY2NjU1QjAyNTc4NkRBRTE1M0VDNDI3MzBGMjMxQ0FGIn0.cIs6KGjRGTHaWX_uFTts_V2a3YcBb7LA0jNOBTZeyDmpPQgRlcABnuYkWUIdjUdR6VYnDitFRV-XK2ZSq6Pk_ZgyfvJ3yRzvWGYjXMu7Nq7MLpVvUh9mLKSbKvlqunW6YVamHSCAbYS8-D_pY9fpWxIcXw0qbwA2XfTdzr0Mrw7ntrkdyK7O1QqWamnEHCmpLfJ2XJlQsU0KaD8FjkL76pO3lWmrca3VYnTmjP1Oo1HEhbK3nImtrNeL2khAyb8ns8ROj2HX41IDNK1aHWPfn9J04pgH3AfBfcwhhqZkrKjTVFQAkSYzuvjKPWOfpgYmBMw3Y5nG_PDf-zlvVPrdpQ; Max-Age=1200; HttpOnly"
            },
            {
              "name": "Date",
              "value": "Fri, 19 May 2023 16:16:37 GMT"
            },
            {
              "name": "Content-Length",
              "value": "0"
            }
          ],
          "cookies": [
            {
              "name": "refreshToken",
              "value": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQMmY2RHg1RWxlYTF5THBUaVpEejBaS3Z1dk1FUkFPZEtBVGkwNDZSc2JNPSIsImF1ZCI6InN0ZWVsaXgiLCJpc3MiOiJzdGVlbGl4IiwiZXhwIjoiMTY4NTExNzc5NyIsImp0aSI6IjIwMUQzODZDNTRBQzlEOUMwRjdCODFBMDVDNDlFQTE1In0.SbxFgEAtZbh0zS-SXZmrVW9iLk-cFz6HcDMU0FHNl-K9BwCeb_boc5igEgImMSYK-NBVQZh1km7YknE-jkBWyF0rIYjSnTzjNUHHwMnn0jE1N-dtEfNRnF1OT0R2bxPSz8gmhtJ3B839xa-jh9uMPMkXEB8BYtABgPH1FqBdijHPUtRVKq6C3ulVleurp2eyF8EHpGLc9rr5wBYSFBk0HQ3FNjjUxfRQLDnzl2xYovoQ2em4grExnkdACxCSpXNtF5bQ7lCnEZyf7-CehrRNwZCpteGKj5ux_wrX_nxma3OEWwrlatML_j-e420TM1tub0C9Ymyt0bMugHw8vaiOGA"
            },
            {
              "name": "accessToken",
              "value": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQMmY2RHg1RWxlYTF5THBUaVpEejBaS3Z1dk1FUkFPZEtBVGkwNDZSc2JNPSIsImF1ZCI6InN0ZWVsaXgiLCJpc3MiOiJzdGVlbGl4IiwiZXhwIjoiMTY4NDUxNDE5NyIsImp0aSI6IjY2NjU1QjAyNTc4NkRBRTE1M0VDNDI3MzBGMjMxQ0FGIn0.cIs6KGjRGTHaWX_uFTts_V2a3YcBb7LA0jNOBTZeyDmpPQgRlcABnuYkWUIdjUdR6VYnDitFRV-XK2ZSq6Pk_ZgyfvJ3yRzvWGYjXMu7Nq7MLpVvUh9mLKSbKvlqunW6YVamHSCAbYS8-D_pY9fpWxIcXw0qbwA2XfTdzr0Mrw7ntrkdyK7O1QqWamnEHCmpLfJ2XJlQsU0KaD8FjkL76pO3lWmrca3VYnTmjP1Oo1HEhbK3nImtrNeL2khAyb8ns8ROj2HX41IDNK1aHWPfn9J04pgH3AfBfcwhhqZkrKjTVFQAkSYzuvjKPWOfpgYmBMw3Y5nG_PDf-zlvVPrdpQ"
            }
          ],
          "content": {
            "mimeType": "text/plain",
            "size": 0,
            "text": ""
          },
          "redirectURL": "",
          "headersSize": 1347,
          "bodySize": 1748
        },
        "cache": {},
        "timings": {
          "blocked": 0,
          "dns": 0,
          "connect": 0,
          "ssl": 0,
          "send": 0,
          "wait": 13,
          "receive": 0
        },
        "time": 13,
        "_securityState": "insecure",
        "serverIPAddress": "0.0.0.0",
        "connection": "8081"
      }
    ]
  }
}

响应似乎有 cookie,但它们没有被保存。

并且对身份验证服务器的下一个请求没有添加任何 cookie。


正确答案


tl;dr

  1. cookie 不会在 0.0.0.0localhost 之间共享。
  2. 会话 cookie 和普通 cookie 都可以在 http://localhost:8080http://localhost:8081 之间共享。
  3. http://localhost:8080/ 页面发送到 http://localhost:8081/ 的请求将被视为跨域请求。
  4. fetch 发送的跨域请求应使用 credentials: 'include' 进行初始化,以使浏览器保存 cookie。

har显示网页的url为http://localhost:8080/,但登录端点为http://0.0.0.0:8081/login0.0.0.0 的 cookie 不会与 localhost 共享。

您可以运行下面的演示来观察行为:

  1. 运行演示:go run main.go;

  2. 在浏览器中打开http://localhost:8080/。该网页将执行以下操作:

    1. 它向http://0.0.0.0:8081/login1发送请求(目的是验证0.0.0.0的cookie不会与localhost共享;
    2. 它向 http://localhost:8081/login2 发送请求(目的是验证会话 cookie 将在 http://localhost:8080http://localhost:8081 之间共享;
    3. 它向http://localhost:8081/login3发送请求(目的是验证正常的cookie将在http://localhost:8080http://localhost:8081之间共享;
    4. 它导航到 http://localhost:8080/resource 并且服务器将转储请求。表明这个头被发送到服务器:cookie:login2=localhost-session; login3=localhost

注释credentials: 'include' 要求将 access-control-allow-origin 标头设置为确切的来源(这意味着 * 将被拒绝),并且 access- control-allow-credentials 标头设置为 true

package main

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
)

func setHeader(w http.ResponseWriter, cookieName, cookieValue string, maxAge int) {
    w.Header().Set("Access-Control-Allow-Origin", "http://localhost:8080")
    w.Header().Set("Access-Control-Allow-Credentials", "true")
    http.SetCookie(w, &http.Cookie{
        Name:     cookieName,
        Value:    cookieValue,
        MaxAge:   maxAge,
        HttpOnly: true,
    })
}

func main() {
    muxWeb := http.NewServeMux()
    // serve the HTML page.
    muxWeb.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        _, err := w.Write([]byte(page))
        if err != nil {
            panic(err)
        }
    }))
    // Dump the request to see what cookies is sent to the server.
    muxWeb.Handle("/resource", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        dump, err := httputil.DumpRequest(r, false)
        if err != nil {
            panic(err)
        }
        _, _ = w.Write(dump)
    }))
    web := &http.Server{
        Addr:    ":8080",
        Handler: muxWeb,
    }
    go func() {
        log.Fatal(web.ListenAndServe())
    }()

    muxAPI := http.NewServeMux()
    muxAPI.Handle("/login1", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        setHeader(w, "login1", "0.0.0.0", 1200)
    }))
    muxAPI.Handle("/login2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        setHeader(w, "login2", "localhost-session", 0)
    }))
    muxAPI.Handle("/login3", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        setHeader(w, "login3", "localhost", 1200)
    }))
    api := &http.Server{
        Addr:    ":8081",
        Handler: muxAPI,
    }
    go func() {
        log.Fatal(api.ListenAndServe())
    }()

    fmt.Println("Open http://localhost:8080/ in the browser")

    select {}
}

var page string = `
<!DOCTYPE html>
<html>
  <body>
    <script type="module">
      async function login(url) {
        const response = await fetch(url, {
          mode: 'cors',
          credentials: 'include',
        });
      }
      await login('http://0.0.0.0:8081/login1');
      await login('http://localhost:8081/login2');
      await login('http://localhost:8081/login3');

      window.location = '/resource';
    </script>
  </body>
</html>
`
卓越飞翔博客
上一篇: 使用 s3 文件创建 gopacket 的 packetSource
下一篇: 返回列表
留言与评论(共有 0 条评论)
   
验证码:
隐藏边栏