feat: 实现Token刷新机制,缩短登录有效期
安全改进: - Access Token有效期从7天缩短为2小时 - 添加Refresh Token机制(有效期7天) - 关闭浏览器后较快失效,提升安全性 后端修改(auth.js): - 添加generateRefreshToken函数生成刷新令牌 - 添加refreshAccessToken函数验证并刷新access token - 分离ACCESS_TOKEN_EXPIRES和REFRESH_TOKEN_EXPIRES配置 后端修改(server.js): - 登录时返回refreshToken和expiresIn - 添加/api/refresh-token接口用于刷新token - Cookie有效期同步调整为2小时 前端修改(app.js): - 保存refreshToken到localStorage - 添加自动刷新定时器(过期前5分钟刷新) - 页面加载时若token过期自动尝试刷新 - 登出时清除refreshToken和定时器 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@ createApp({
|
||||
isLoggedIn: false,
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
tokenRefreshTimer: null,
|
||||
|
||||
// 视图状态
|
||||
currentView: 'files',
|
||||
@@ -523,6 +525,7 @@ handleDragLeave(e) {
|
||||
|
||||
if (response.data.success) {
|
||||
this.token = response.data.token;
|
||||
this.refreshToken = response.data.refreshToken;
|
||||
this.user = response.data.user;
|
||||
this.isLoggedIn = true;
|
||||
this.showResendVerify = false;
|
||||
@@ -534,8 +537,13 @@ handleDragLeave(e) {
|
||||
|
||||
// 保存token到localStorage
|
||||
localStorage.setItem('token', this.token);
|
||||
localStorage.setItem('refreshToken', this.refreshToken);
|
||||
localStorage.setItem('user', JSON.stringify(this.user));
|
||||
|
||||
// 启动token自动刷新(在过期前5分钟刷新)
|
||||
const expiresIn = response.data.expiresIn || (2 * 60 * 60 * 1000);
|
||||
this.startTokenRefresh(expiresIn);
|
||||
|
||||
// 直接从登录响应中获取存储信息
|
||||
this.storagePermission = this.user.storage_permission || 'sftp_only';
|
||||
this.storageType = this.user.current_storage_type || 'sftp';
|
||||
@@ -976,7 +984,10 @@ handleDragLeave(e) {
|
||||
this.isLoggedIn = false;
|
||||
this.user = null;
|
||||
this.token = null;
|
||||
this.refreshToken = null;
|
||||
this.stopTokenRefresh();
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('lastView');
|
||||
this.showResendVerify = false;
|
||||
@@ -1002,10 +1013,12 @@ handleDragLeave(e) {
|
||||
// 检查本地存储的登录状态
|
||||
async checkLoginStatus() {
|
||||
const token = localStorage.getItem('token');
|
||||
const refreshToken = localStorage.getItem('refreshToken');
|
||||
const user = localStorage.getItem('user');
|
||||
|
||||
if (token && user) {
|
||||
this.token = token;
|
||||
this.refreshToken = refreshToken;
|
||||
this.user = JSON.parse(user);
|
||||
|
||||
// 先验证token是否有效
|
||||
@@ -1031,6 +1044,9 @@ handleDragLeave(e) {
|
||||
|
||||
console.log('[页面加载] Token验证成功,存储权限:', this.storagePermission, '存储类型:', this.storageType);
|
||||
|
||||
// 启动token自动刷新(假设剩余1.5小时,实际由服务端控制)
|
||||
this.startTokenRefresh(1.5 * 60 * 60 * 1000);
|
||||
|
||||
// 启动定期检查用户配置
|
||||
this.startProfileSync();
|
||||
// 加载用户主题设置
|
||||
@@ -1052,29 +1068,106 @@ handleDragLeave(e) {
|
||||
// 强制切换到目标视图并加载数据
|
||||
this.switchView(targetView, true);
|
||||
} else {
|
||||
// 响应异常,清除登录状态
|
||||
this.handleTokenExpired();
|
||||
// 响应异常,尝试刷新token
|
||||
await this.tryRefreshOrLogout();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[页面加载] Token验证失败:', error.response?.status || error.message);
|
||||
// token无效或过期,清除登录状态
|
||||
// token无效或过期,尝试使用refresh token刷新
|
||||
if (error.response?.status === 401 && this.refreshToken) {
|
||||
console.log('[页面加载] 尝试使用refresh token刷新...');
|
||||
const refreshed = await this.doRefreshToken();
|
||||
if (refreshed) {
|
||||
// 刷新成功,重新检查登录状态
|
||||
await this.checkLoginStatus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 刷新失败或无refresh token,清除登录状态
|
||||
this.handleTokenExpired();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 尝试刷新token,失败则登出
|
||||
async tryRefreshOrLogout() {
|
||||
if (this.refreshToken) {
|
||||
const refreshed = await this.doRefreshToken();
|
||||
if (refreshed) {
|
||||
await this.checkLoginStatus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.handleTokenExpired();
|
||||
},
|
||||
|
||||
// 处理token过期/失效
|
||||
handleTokenExpired() {
|
||||
console.log('[认证] Token已失效,清除登录状态');
|
||||
this.isLoggedIn = false;
|
||||
this.user = null;
|
||||
this.token = null;
|
||||
this.refreshToken = null;
|
||||
this.stopTokenRefresh();
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('lastView');
|
||||
this.stopProfileSync();
|
||||
},
|
||||
|
||||
// 启动token自动刷新定时器
|
||||
startTokenRefresh(expiresIn) {
|
||||
this.stopTokenRefresh(); // 先清除旧的定时器
|
||||
|
||||
// 在token过期前5分钟刷新
|
||||
const refreshTime = Math.max(expiresIn - 5 * 60 * 1000, 60 * 1000);
|
||||
console.log(`[认证] Token将在 ${Math.round(refreshTime / 60000)} 分钟后刷新`);
|
||||
|
||||
this.tokenRefreshTimer = setTimeout(async () => {
|
||||
await this.doRefreshToken();
|
||||
}, refreshTime);
|
||||
},
|
||||
|
||||
// 停止token刷新定时器
|
||||
stopTokenRefresh() {
|
||||
if (this.tokenRefreshTimer) {
|
||||
clearTimeout(this.tokenRefreshTimer);
|
||||
this.tokenRefreshTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
// 执行token刷新
|
||||
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
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
this.token = response.data.token;
|
||||
localStorage.setItem('token', this.token);
|
||||
console.log('[认证] Token刷新成功');
|
||||
|
||||
// 继续下一次刷新
|
||||
const expiresIn = response.data.expiresIn || (2 * 60 * 60 * 1000);
|
||||
this.startTokenRefresh(expiresIn);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[认证] Token刷新失败:', error.response?.data?.message || error.message);
|
||||
// 刷新失败,需要重新登录
|
||||
this.handleTokenExpired();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// 检查URL参数
|
||||
checkUrlParams() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
Reference in New Issue
Block a user