为什么我的 cookie 没有被发送? ReactJS front-end、Go back-end

Why is my cookie not being sent? ReactJS front-end, Go back-end

我正在使用 Go back-end(go-fiber 框架)和 ReactJS front-end.

开发个人理财应用程序

我的身份验证方法是 return 在用户登录时将 JWT 作为 cookie。

前端使用fetch发送了一个sign-in请求,然后又跟进了另一个fetch获取用户数据。 fetch 调用以及服务器处理函数可以在本问题末尾的附录中找到。

当我对此进行测试时,我获得了成功 sign-in。 Set-Cookie header 是 returned 并且我在响应中看到了 cookie,正如我所期望的那样。但是,JWT 未作为 header 包含在用户数据请求中。作为已解析 JWT 的处理程序 returns {"status": "unauthorized"}nil.

为什么 JWT 没有包含在用户数据请求中?

这是 Set-Cookie header,以及所有 Sign-In 响应 header 的屏幕截图。 jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDY1MTczOTMsImlzcyI6IjE3In0.SDKnxjsVImuVOHw_hnsPX1ZhtS7-_6s8Cqk79SwniCY; expires=Sat, 05 Mar 2022 21:56:33 GMT; path=/; HttpOnly; SameSite=Lax

Sign-In Response Headers

这是 return 在 sign-in 上编辑的 JWT cookie,以及来自 Chrome 开发人员工具的 cookie 的屏幕截图。 jwt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDY1MTczOTMsImlzcyI6IjE3In0.SDKnxjsVImuVOHw_hnsPX1ZhtS7-_6s8Cqk79SwniCY localhost / 2022-03-05T21:56:33.000Z 195 ✓ Lax Medium

Sign-In Response Cookie

我在“应用程序”选项卡的“Cookies”部分没有看到任何内容。但是,我在其他地方读到,我不应该期望在此处看到 httpOnly 设置为 true 的任何 cookie。

Application Cookies

我期待在用户数据请求中看到一个名为“Cookies”的 header。但我只看到这些:

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: localhost:9000
Origin: http://localhost:3000
Referer: http://localhost:3000/
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36

User Data Request Headers

非常感谢任何提示或帮助。以下是 front-end 和 back-end 代码的 GitHub 页面的链接,以防它有助于解释问题:

ReactJS Front-End

Go-Fiber Back-End

附录

Sign-In 请求 fetch:

        fetch('http://localhost:9000/signIn', requestOptions)
            .then(res => {
                if (res.status === 200) {
                    res.json()
                        .then(
                            (result) => {
                                if (result.status && result.status === "success") {
                                    this.props.onRouteChange('home');
                                } else {
                                    // TO DO: display failure message on UI
                                    console.log('Failed to sign in');
                                }
                            },
                            (error) => {
                                this.setState({
                                    isLoaded: true,
                                    error
                                });
                            }
                        );
                } else {
                    console.log('Error signing in');
                    res.json()
                        .then(
                            (result) => {
                                console.log(result);
                            },
                            (error) => {
                                console.log('Error reading JSON of response with status !== 200');
                                console.log(error);
                            }
                        );
                }
            });

Sign-In 处理函数:

func handleSignIn(c *fiber.Ctx) error {
    // unmarshal received sign in data into User struct
    var signIn User
    if err := c.BodyParser(&signIn); err != nil {
        err = fmt.Errorf("failed to process HTTP request body to /signIn: %w", err)
        log.Println("Error:", err)
        c.Status(fiber.StatusBadRequest)
        return err
    }

    // look for the identified user in the users database
    usr, err := getUserByUsername(signIn.Username)
    if err != nil && err == sql.ErrNoRows {
        log.Println("Error: user", signIn.Username, "attempted to sign in but not found in users database")
        c.Status(fiber.StatusBadRequest)
        return fmt.Errorf("invalid username/password combination")
    }
    if err != nil {
        err = fmt.Errorf("failed to query database for user %s: %w", signIn.Username, err)
        log.Println("Error:", err)
        c.Status(fiber.StatusInternalServerError)
        return err
    }

    // hash the given password for comparison with the recorded password
    err = bcrypt.CompareHashAndPassword([]byte(usr.Password), []byte(signIn.Password))
    if err != nil {
        log.Println("CompareHashAndPassword returned error during sign in attempt:", err)
        c.Status(fiber.StatusBadRequest)
        return fmt.Errorf("invalid username/password combination")
    }

    // declare claims for the JWT that will be sent back
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
        Issuer:    strconv.Itoa(int(usr.Id)),
        ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), // 1 day
    })
    if token == nil {
        err = fmt.Errorf("failed to instantiate JWT")
        log.Println("Error:", err)
        c.Status(fiber.StatusInternalServerError)
        return err
    }

    // encrypt the JWT with the private key
    tokenString, err := token.SignedString([]byte(jwtPrivateKey))
    if err != nil {
        err = fmt.Errorf("failed to encrypt JWT: %w", err)
        log.Println("Error:", err)
        c.Status(fiber.StatusInternalServerError)
        return err
    }

    c.Cookie(&fiber.Cookie{
        Name:     "jwt",
        Value:    tokenString,
        Expires:  time.Now().Add(time.Hour * 24),
        HTTPOnly: true,
    })

    // send response
    return c.JSON(fiber.Map{
        "status": "success",
    })
}

用户数据请求获取:

    componentDidMount() {
        fetch("http://localhost:9000/getExpenses")
            .then(res => res.json())
            .then(
                (result) => {
                    if (result.status !== null && result.status === "unauthorized") {
                        console.log('Failed authorization when requesting expenses!');
                    } else if (result.expenses === null) {
                        console.log('Response did not contain expenses map');
                    } else {
                        this.setState({
                            isLoaded: true,
                            expenses: result.expenses
                        });
                    }
                },
                (error) => {
                    this.setState({
                        isLoaded: true,
                        error
                    });
                }
            );
    }

用户数据请求处理程序:

func handleGetExpenses(c *fiber.Ctx) error {
    // parse JWT from HTTP cookie
    token, err := parseCookie(c)
    if err != nil {
        c.Status(fiber.StatusUnauthorized)
        return c.JSON(fiber.Map{
            "status": "unauthorized",
        })
    }

    // check which user is getting their expenses
    claims := token.Claims.(*jwt.StandardClaims)
    userId, err := strconv.ParseInt(claims.Issuer, 10, 64)
    if err != nil {
        err = fmt.Errorf("invalid Issuer field in JWT")
        log.Println("Error:", err)
        c.Status(fiber.StatusUnauthorized)
        return err
    }

    // get all expenses from the database
    expenses, err := getAllExpenses(userId)
    if err != nil {
        err = fmt.Errorf("failed to get expenses from expense table: %w", err)
        log.Println("Error:", err)
        c.Status(fiber.StatusInternalServerError)
        return err
    }

    // send response
    return c.JSON(fiber.Map{
        "expenses": expenses,
    })
}

默认情况下,fetch 不使用 cookie。您可以让 fetch 使用这样的 cookie:

fetch(url, {
  credentials: "same-origin",
}).then(responseHandler).catch(errorHandler);

您可以查看文档了解更多详情: https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters