我目前正在研究用户身份验证并将 JWT 令牌存储在 MongoDB 会话集合中。但是,我在管理多个会话时遇到了问题。具体来说,在第一个用户成功登录后,我无法为每个后续用户生成和存储会话。
我在 VS Code 和 MongoDB Compass 中使用 Thunder Client。当我使用以下凭据成功登录时:
{
"email": "[email protected]",
"password": "asdfAsdf1!"
}
成功创建会话并将其存储在会话集合中,并生成 connect.sid cookie。但是,当我尝试使用一组不同的凭据登录时:
{
"email": "[email protected]",
"password": "asdfAsdf2!"
}
并且与 [电子邮件受保护] 的凭据关联的会话 ID 正在重新生成为新 ID,而不是创建全新的会话且不更改之前生成的会话。
请提供如何解决此问题的建议?
这是我的代码
app.use(
session({
secret: "key_that_will_sign_the_cookie",
resave: false,
saveUninitialized: false,
store: MongoStore.create({
mongoUrl: "mongodb://127.0.0.1:27017/addressBook",
collectionName: "sessions",
}),
cookie: {
secure: false,
maxAge: 15 * 60 * 1000,
},
})
);
userController.ts
export const logIn = asyncHandler(async (req: Request, res: Response) => {
const { email, password } = req.body;
const user = await userService.login(email, password);
const token = tokenService.generateToken(
user._id,
"ACCESS_TOKEN_PRIVATE_KEY_HERE"
);
// Set session
(req.session as MySession).jwtToken = token;
res.status(200).json({
success: true,
message: "You have successfully logged in.",
});
});
export const logout = asyncHandler(async (req: Request, res: Response) => {
req.session.destroy((err) => {
if (err) {
console.log("Error destroying session", err.message);
res.status(500).json({
success: false,
message: "Error logging out.",
});
} else {
res.clearCookie("connect.sid");
res
.status(200)
.json({ success: true, message: "Successfully logged out." });
}
});
});
requireAuth.ts(中间件)
const requireAuth = async (req: Request, res: Response, next: NextFunction) => {
try {
const { jwtToken } = (req.session as MySession) || {};
if (!jwtToken) {
return res.status(401).json({ messagE: messages.unauthorized });
}
const { sub: userId } = jwt.verify(
jwtToken,
"ACCESS_TOKEN_PRIVATE_KEY_HERE"
);
const user = await User.findOne({ _id: userId });
if (!user) {
return res.status(401).json({ messagE: messages.unauthorized });
}
req.user = user;
next();
} catch (error: any) {
return res.status(401).json({ message: error.message });
}
};
更新 @jfriend00
使用会话背后的想法是组织代码并将逻辑拆分为多个文件。业务逻辑服务,但在我现在的情况下可能是多余的(稍后我将重构代码)。
jwt
是 jsonwebtoken,我使用它来生成身份验证令牌,并将其存储在会话中 - 参见屏幕截图。稍后在 requireAuth
中间件中,我验证该 jwt 令牌的有效性并用于保护所有联系路由。
router.use(requireAuth);
router
.get("/", getAllContacts)
.get("/favorite", getFavoriteContacts)
.get("/:contactId", getContactById)
.post("/", addNewContact)
.patch("/:contactId", updateContact)
.delete("/:contactId", deleteContact);
tokenService.ts
import jwt from "jsonwebtoken";
import mongoose from "mongoose";
export const generateToken = (
userId: mongoose.Types.ObjectId,
secret: string
): string => {
const payload = {
sub: userId,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 15 * 60,
};
return jwt.sign(payload, secret);
};
export default { generateToken };
userService.ts
const login = async (email: string, password: string): Promise<IUserDoc> => {
loginValidation(email, password);
const user = await getUserByEmail(email);
const isValidUser = user && (await user.isPasswordMatch(password));
if (!isValidUser) {
throw new ApiError(400, messages.incorrectCredentials);
}
return user;
};
屏幕截图中捕获的会话是我使用一名用户登录时存储的会话(例如电子邮件:[email protected])。随后,当我输入第二个用户的凭据并单击“登录”时,为前一个用户存储的会话的到期日期将被更新,并且还会生成新的 jwtToken。为什么会发生这种情况?是否应该独立于第一个会话创建新会话?另外我想提一下 cookie connect.sid
和以前一样,没有改变。
对于之前的任何困惑,我深表歉意。
如果您有明确的“注销”功能,您可以清除或删除先前的会话,但您不能指望这一点,因为用户在以其他人身份登录之前不需要这样做。也许您应该做的是,当您登录用户时,您应该首先将会话对象重新初始化为空(清除所有先前的数据),这样就不存在从一个用户到下一个用户的污染风险。请注意,从不同浏览器登录之间这都不是问题,因为它们永远不会共享相同的会话对象。
是否应该独立于第一个会话创建新会话?另外我想提一下 cookie connect.sid 与以前保持相同,没有改变。
来自express-session的会话对象与登录无关。您的登录是会话之上的一个完全不同的层。会话对象连接到
connect.sid
cookie。如果客户端提供相同的
connect.sid
cookie,那么您的服务器将获取相同的会话对象。因此,正如我之前所解释的,如果您在浏览器 1 中以 userA 身份登录,则会将 userA 的登录数据填充到与浏览器 1 中的 cookie 关联的会话对象中。如果您随后在会话 cookie 之前在浏览器 1 中以 userB 身份登录过期后,它仍然具有相同的 connect.sid
cookie,因此它仍然会在服务器上使用完全相同的会话对象。然后,您的代码会将 userB 的数据放入同一个会话对象中。请记住,express-session 是在特定浏览器中处理 cookie,与您自己的登录过程完全无关。您的登录过程只是使用底层会话对象来存储登录数据。