diff --git a/backend/server.js b/backend/server.js index ccec88c..c817d3c 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1652,13 +1652,23 @@ app.post('/api/login', // 增强Cookie安全设置 const isSecureEnv = process.env.COOKIE_SECURE === 'true'; - res.cookie('token', token, { + const cookieOptions = { httpOnly: true, secure: isSecureEnv, - // HTTPS环境使用strict,HTTP环境使用lax(开发环境兼容) sameSite: isSecureEnv ? 'strict' : 'lax', - maxAge: 2 * 60 * 60 * 1000, // 2小时(与access token有效期一致) - path: '/' // 限制Cookie作用域 + path: '/' + }; + + // 设置 access token Cookie(2小时有效) + res.cookie('token', token, { + ...cookieOptions, + maxAge: 2 * 60 * 60 * 1000 + }); + + // 设置 refresh token Cookie(7天有效) + res.cookie('refreshToken', refreshToken, { + ...cookieOptions, + maxAge: 7 * 24 * 60 * 60 * 1000 }); // 记录登录成功日志 @@ -1667,8 +1677,6 @@ app.post('/api/login', res.json({ success: true, message: '登录成功', - token, - refreshToken, // 返回refresh token expiresIn: 2 * 60 * 60 * 1000, // 告知前端access token有效期(毫秒) user: { id: user.id, @@ -1694,9 +1702,10 @@ app.post('/api/login', } ); -// 刷新Access Token +// 刷新Access Token(从 HttpOnly Cookie 读取 refreshToken) app.post('/api/refresh-token', (req, res) => { - const { refreshToken } = req.body; + // 优先从 Cookie 读取,兼容从请求体读取(向后兼容) + const refreshToken = req.cookies?.refreshToken || req.body?.refreshToken; if (!refreshToken) { return res.status(400).json({ @@ -1720,21 +1729,21 @@ app.post('/api/refresh-token', (req, res) => { httpOnly: true, secure: isSecureEnv, sameSite: isSecureEnv ? 'strict' : 'lax', - maxAge: 2 * 60 * 60 * 1000, // 2小时 + maxAge: 2 * 60 * 60 * 1000, path: '/' }); res.json({ success: true, - token: result.token, expiresIn: 2 * 60 * 60 * 1000 }); }); // 登出(清除Cookie) app.post('/api/logout', (req, res) => { - // 清除认证Cookie + // 清除所有认证Cookie res.clearCookie('token', { path: '/' }); + res.clearCookie('refreshToken', { path: '/' }); res.json({ success: true, message: '已登出' }); }); diff --git a/frontend/app.js b/frontend/app.js index ef16b86..e7a78f4 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -10,8 +10,7 @@ createApp({ // 用户状态 isLoggedIn: false, user: null, - token: null, - refreshToken: null, + token: null, // 仅用于内部状态跟踪,实际认证通过 HttpOnly Cookie tokenRefreshTimer: null, // 视图状态 @@ -527,8 +526,7 @@ handleDragLeave(e) { const response = await axios.post(`${this.apiBase}/api/login`, this.loginForm); if (response.data.success) { - this.token = response.data.token; - this.refreshToken = response.data.refreshToken; + // token 和 refreshToken 都通过 HttpOnly Cookie 自动管理 this.user = response.data.user; this.isLoggedIn = true; this.showResendVerify = false; @@ -1002,7 +1000,6 @@ handleDragLeave(e) { this.isLoggedIn = false; this.user = null; this.token = null; - this.refreshToken = null; this.stopTokenRefresh(); localStorage.removeItem('user'); localStorage.removeItem('lastView'); @@ -1086,12 +1083,11 @@ handleDragLeave(e) { // 尝试刷新token,失败则登出 async tryRefreshOrLogout() { - if (this.refreshToken) { - const refreshed = await this.doRefreshToken(); - if (refreshed) { - await this.checkLoginStatus(); - return; - } + // refreshToken 通过 Cookie 自动管理,直接尝试刷新 + const refreshed = await this.doRefreshToken(); + if (refreshed) { + await this.checkLoginStatus(); + return; } this.handleTokenExpired(); }, @@ -1102,7 +1098,6 @@ handleDragLeave(e) { this.isLoggedIn = false; this.user = null; this.token = null; - this.refreshToken = null; this.stopTokenRefresh(); localStorage.removeItem('user'); localStorage.removeItem('lastView'); @@ -1130,18 +1125,12 @@ handleDragLeave(e) { } }, - // 执行token刷新(通过 refreshToken 刷新 HttpOnly Cookie 中的 access token) + // 执行token刷新(refreshToken 通过 HttpOnly Cookie 自动发送) async doRefreshToken() { - if (!this.refreshToken) { - console.log('[认证] 无refresh token,无法刷新'); - return false; - } - try { console.log('[认证] 正在刷新access token...'); - const response = await axios.post(`${this.apiBase}/api/refresh-token`, { - refreshToken: this.refreshToken - }); + // refreshToken 通过 Cookie 自动携带,无需手动传递 + const response = await axios.post(`${this.apiBase}/api/refresh-token`); if (response.data.success) { // 后端已自动更新 HttpOnly Cookie 中的 token