我正在开发一个带有 Go 后端(go-fiber 框架)和 ReactJS 前端的个人理财应用程序。
我的身份验证方法是在用户登录时将 JWT 作为 cookie 返回。
前端使用 发送登录请求fetch
,然后跟进另一个fetch
获取用户数据。这些fetch
调用以及服务器处理程序函数可以在本问题末尾的附录中找到。
当我对此进行测试时,我会成功登录。返回一个 Set-Cookie 标头,我在响应中看到了我所期望的 cookie。但是,JWT 并未作为标头包含在对用户数据的请求中。处理程序返回{"status": "unauthorized"}
解析的 JWT 是nil
.
为什么 JWT 未包含在用户数据请求中?
这是 Set-Cookie 标头,以及所有登录响应标头的屏幕截图。
jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDY1MTczOTMsImlzcyI6IjE3In0.SDKnxjsVImuVOHw_hnsPX1ZhtS7-_6s8Cqk79SwniCY; expires=Sat, 05 Mar 2022 21:56:33 GMT; path=/; HttpOnly; SameSite=Lax
这是登录时返回的 JWT cookie,以及来自 Chrome 开发者工具的 cookie 的屏幕截图。
jwt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDY1MTczOTMsImlzcyI6IjE3In0.SDKnxjsVImuVOHw_hnsPX1ZhtS7-_6s8Cqk79SwniCY localhost / 2022-03-05T21:56:33.000Z 195 ✓ Lax Medium
我在“应用程序”选项卡的“Cookies”部分没有看到任何内容。但是,我在其他地方读到,我不应该期望在httpOnly
这里看到任何设置为 true 的 cookie。
我希望在用户数据请求中看到一个名为“Cookies”的标头。但我只看到这些:
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
非常感谢任何轻推或帮助。以下是前端和后端代码的 GitHub 页面链接,以帮助解释问题:
附录
登录请求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);
}
);
}
});
登录处理函数:
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,
})
}