feat: 添加JWT登录鉴权和用户管理功能

- 后端功能:
  - 实现JWT登录鉴权,token有效期7天,超过1天自动刷新
  - 添加用户管理功能,支持创建、删除、修改密码
  - 首次创建的用户为默认账户,拥有管理权限
  - 实现密码重置功能,支持重置密钥和频率限制(1分钟最多3次)
  - 所有API接口需要JWT鉴权
  - WebSocket连接需要JWT鉴权,绑定用户身份

- 前端功能:
  - 添加登录页面和密码重置页面
  - 添加用户管理页面,默认账户可管理所有用户,普通用户只能查看和修改自己
  - 添加退出登录功能,带二次确认
  - 未登录时不建立WebSocket连接
  - API请求自动携带JWT token,认证失败自动跳转登录页

- 安全特性:
  - 密码使用BCrypt加密存储
  - 重置密码错误信息统一处理,避免信息泄露
  - 用户操作严格绑定JWT,防止数据篡改和越权
This commit is contained in:
WrBug
2025-12-03 00:24:46 +08:00
parent f1c9b34488
commit dd553ae160
30 changed files with 2402 additions and 47 deletions
+29 -5
View File
@@ -53,6 +53,13 @@ class WebSocketManager {
* 连接 WebSocket(全局共享连接)
*/
connect(): void {
// 检查是否有token,未登录不允许连接
const token = this.getToken()
if (!token) {
console.log('[WebSocket] 未登录,不建立连接')
return
}
// 如果已经连接或正在连接,直接返回
if (this.ws?.readyState === WebSocket.OPEN || this.isConnecting) {
return
@@ -104,8 +111,8 @@ class WebSocketManager {
this.isConnecting = false
this.notifyConnectionStatus(false)
this.stopPing()
// 自动重连(除非正在卸载)
if (!this.isUnmounting) {
// 自动重连(除非正在卸载或未登录
if (!this.isUnmounting && this.getToken()) {
this.scheduleReconnect()
}
}
@@ -113,8 +120,8 @@ class WebSocketManager {
console.error('[WebSocket] 创建连接失败:', error)
this.isConnecting = false
this.notifyConnectionStatus(false)
// 自动重连(除非正在卸载)
if (!this.isUnmounting) {
// 自动重连(除非正在卸载或未登录
if (!this.isUnmounting && this.getToken()) {
this.scheduleReconnect()
}
}
@@ -272,6 +279,11 @@ class WebSocketManager {
return
}
// 检查是否有token,未登录不重连
if (!this.getToken()) {
return
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
}
@@ -312,14 +324,26 @@ class WebSocketManager {
}
/**
* 获取 WebSocket URL
* 获取 WebSocket URL(带token认证)
*/
private getWebSocketUrl(): string {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const host = window.location.host
const token = this.getToken()
if (token) {
// 通过查询参数传递token
return `${protocol}//${host}/ws?token=${encodeURIComponent(token)}`
}
return `${protocol}//${host}/ws`
}
/**
* 获取token(从localStorage
*/
private getToken(): string | null {
return localStorage.getItem('jwt_token')
}
/**
* 注册连接状态回调
*/