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

xqk2d5yq  于 2023-05-20  发布在  Go
关注(0)|答案(1)|浏览(129)

我在聊天应用程序中遇到基于cookie的令牌认证问题。我使用Go后端和标准的网络库来向响应cookie添加令牌。当用户通过密码验证(通过POST到auth服务器上的/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从localhost:8080传输到localhost:8081。我计划实施某种模拟认证,以避免阅读开发环境的cookie来解决这个问题,但我不确定这是否是我的问题的原因。只是想再看一眼,看看我能不能让它工作,而不需要这样做。
更新:我已经查看了开发工具中的网络选项卡:图像显示登录后的响应返回cookie,但它们随后不会发送到端口8081上的auth服务器。我也看过cookie存储后,得到200响应登录,有没有cookie目前,即使在收到他们的响应。我正在使用Firefox的私人模式访问该网站。请注意,cookie不包括MaxAge,即使我在Go代码中设置了MaxAge,这似乎是一个问题。
更新:这是登录后的HAR文件。您可以看到响应具有Max-Age,但之后它不会显示在cookie选项卡中。

{
  "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,但它们不会被保存。

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

dced5bon

dced5bon1#

TL;DR

  1. cookie不会在0.0.0.0localhost之间共享。
    1.会话cookie和普通cookie都可以在http://localhost:8080http://localhost:8081之间共享。
    1.从页面http://localhost:8080/发送到http://localhost:8081/的请求被认为是跨域请求。
  2. fetch发送的跨域请求应该使用credentials: 'include'初始化,以使浏览器保存cookie。
    HAR显示网页的URL为http://localhost:8080/,但登录端点为http://0.0.0.0:8081/login0.0.0.0的Cookie不会与localhost共享。
    您可以运行下面的演示来观察行为:
    1.运行demo:go run main.go ;
    1.在浏览器中打开http://localhost:8080/。网页将做这些事情:
    1.它向http://0.0.0.0:8081/login1发送请求(目的是验证0.0.0.0的cookie不会与localhost共享;
    1.它向http://localhost:8081/login2发送请求(目的是验证会话cookie将在http://localhost:8080http://localhost:8081之间共享;
    1.它向http://localhost:8081/login3发送请求(目的是验证http://localhost:8080http://localhost:8081之间将共享正常cookie;
    1.它导航到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>
`

相关问题