搜索
首页后端开发Golang如何解决 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: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>
`

以上是如何解决 Go 聊天应用程序中 cookie 未在本地主机端口之间传输的问题?的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:stackoverflow。如有侵权,请联系admin@php.cn删除
去其他语言:比较分析去其他语言:比较分析Apr 28, 2025 am 12:17 AM

goisastrongchoiceforprojectsneedingsimplicity,绩效和引发性,butitmaylackinadvancedfeatures and ecosystemmaturity.1)

比较以其他语言的静态初始化器中的初始化功能比较以其他语言的静态初始化器中的初始化功能Apr 28, 2025 am 12:16 AM

Go'sinitfunctionandJava'sstaticinitializersbothservetosetupenvironmentsbeforethemainfunction,buttheydifferinexecutionandcontrol.Go'sinitissimpleandautomatic,suitableforbasicsetupsbutcanleadtocomplexityifoverused.Java'sstaticinitializersoffermorecontr

GO中初始功能的常见用例GO中初始功能的常见用例Apr 28, 2025 am 12:13 AM

thecommonusecasesfortheinitfunctionoare:1)加载configurationfilesbeforeThemainProgramStarts,2)初始化的globalvariables和3)runningpre-checkSorvalidationsbeforEtheprofforeTheProgrecce.TheInitFunctionIsautefunctionIsautomentycalomationalmatomatimationalycalmatemationalcalledbebeforethemainfuniinfuninfuntuntion

GO中的频道:掌握际际交流GO中的频道:掌握际际交流Apr 28, 2025 am 12:04 AM

ChannelsarecrucialingoforenablingsafeandefficityCommunicationBetnewengoroutines.theyfacilitateSynChronizationAndManageGoroutIneLifeCycle,EssentialforConcurrentProgramming.ChannelSallSallSallSallSallowSallowsAllowsEnderDendingAndReceivingValues,ActassignalsignalsforsynChronization,and actassignalsynChronization and andsupppor

包装错误:将上下文添加到错误链中包装错误:将上下文添加到错误链中Apr 28, 2025 am 12:02 AM

在Go中,可以通过errors.Wrap和errors.Unwrap方法来包装错误并添加上下文。1)使用errors包的新功能,可以在错误传播过程中添加上下文信息。2)通过fmt.Errorf和%w包装错误,帮助定位问题。3)自定义错误类型可以创建更具语义化的错误,增强错误处理的表达能力。

使用GO开发时的安全考虑使用GO开发时的安全考虑Apr 27, 2025 am 12:18 AM

Gooffersrobustfeaturesforsecurecoding,butdevelopersmustimplementsecuritybestpracticeseffectively.1)UseGo'scryptopackageforsecuredatahandling.2)Manageconcurrencywithsynchronizationprimitivestopreventraceconditions.3)SanitizeexternalinputstoavoidSQLinj

了解GO的错误接口了解GO的错误接口Apr 27, 2025 am 12:16 AM

Go的错误接口定义为typeerrorinterface{Error()string},允许任何实现Error()方法的类型被视为错误。使用步骤如下:1.基本检查和记录错误,例如iferr!=nil{log.Printf("Anerroroccurred:%v",err)return}。2.创建自定义错误类型以提供更多信息,如typeMyErrorstruct{MsgstringDetailstring}。3.使用错误包装(自Go1.13起)来添加上下文而不丢失原始错误信息,

并发程序中的错误处理并发程序中的错误处理Apr 27, 2025 am 12:13 AM

对效率的Handleerrorsinconcurrentgopragrs,UsechannelstocommunicateErrors,EmparterRorwatchers,InsterTimeouts,UsebufferedChannels和Provideclearrormessages.1)USEchannelelStopassErstopassErrorsErtopassErrorsErrorsFromGoroutInestotheStothemainfunction.2)

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器