# 用户故事1.3实现计划:认证系统现代化 ## 📋 实现目标 **作为** 系统用户,**我想要** 在新系统中安全便捷地登录和管理我的账户,**以便** 我能够正常访问配置管理功能。 ## 🎯 验收标准对照与实现计划 ### ✅ 功能需求实现计划 #### 1. 用户名/密码登录功能 **需求**: 实现用户名/密码登录功能,支持管理员自动登录功能 **当前状态分析**: - ✅ 登录页面基础结构存在 - ✅ 表单组件已创建 - ⚠️ 缺少JWT令牌管理 - ⚠️ 缺少会话管理 - ⚠️ 缺少管理员自动登录功能 **实现计划**: ```typescript // 1. 创建认证Context interface AuthContextType { user: User | null; token: string | null; login: (username: string, password: string) => Promise; logout: () => void; isAuthenticated: boolean; isLoading: boolean; } // 2. 创建登录API接口 interface LoginRequest { username: string; password: string; captcha?: string; } interface LoginResponse { token: string; refreshToken: string; user: User; expiresIn: number; } // 3. 管理员自动登录检测 const autoLoginForAdmin = () => { if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { return { username: 'admin', password: 'admin123' }; } return null; }; ``` **需要创建/修改的文件**: - `src/store/authStore.ts` - Zustand认证状态管理 - `src/hooks/useAuth.ts` - 认证Hook - `src/services/authService.ts` - 认证API服务 - `src/types/auth.ts` - 认证相关类型定义 - `src/components/auth/LoginForm.tsx` - 登录表单组件 - `src/utils/tokenStorage.ts` - 令牌存储工具 #### 2. JWT令牌自动刷新和管理机制 **需求**: 实现 JWT 令牌自动刷新和管理机制,用于安全的会话处理 **实现计划**: ```typescript // tokenStorage.ts interface TokenStorage { getToken(): string | null; setTokens(accessToken: string, refreshToken: string): void; removeTokens(): void; getRefreshToken(): string | null; isTokenExpired(token: string): boolean; } // authService.ts class AuthService { private refreshPromise: Promise | null = null; async refreshToken(): Promise { // 防止并发刷新 if (this.refreshPromise) { return this.refreshPromise; } this.refreshPromise = this.doRefreshToken(); return this.refreshPromise.finally(() => { this.refreshPromise = null; }); } private async doRefreshToken(): Promise { const refreshToken = tokenStorage.getRefreshToken(); if (!refreshToken) { throw new Error('No refresh token available'); } const response = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refreshToken }), }); if (!response.ok) { throw new Error('Token refresh failed'); } const { token, refreshToken: newRefreshToken } = await response.json(); tokenStorage.setTokens(token, newRefreshToken); return token; } } // HTTP拦截器 const createAuthenticatedHttpClient = () => { const client = axios.create({ baseURL: '/api', }); client.interceptors.request.use(async (config) => { let token = tokenStorage.getToken(); if (token && tokenStorage.isTokenExpired(token)) { try { token = await authService.refreshToken(); } catch (error) { // 刷新失败,跳转到登录页 authStore.getState().logout(); return Promise.reject(error); } } if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); return client; }; ``` **需要创建的文件**: - `src/services/httpClient.ts` - HTTP客户端 - `src/utils/tokenStorage.ts` - 令牌存储 - `src/services/authService.ts` - 认证服务 - `src/middleware/authInterceptor.ts` - 认证拦截器 #### 3. 会话超时和异常登录检测 **需求**: 实现会话超时和异常登录检测,增强安全性 **实现计划**: ```typescript // sessionManager.ts interface SessionManager { startSessionMonitoring(): void; stopSessionMonitoring(): void; extendSession(): void; checkSessionValidity(): boolean; } class SessionManagerImpl implements SessionManager { private sessionTimeout: number = 30 * 60 * 1000; // 30分钟 private warningTimeout: number = 25 * 60 * 1000; // 25分钟警告 private lastActivity: number = Date.now(); private warningTimer: NodeJS.Timeout | null = null; private timeoutTimer: NodeJS.Timeout | null = null; startSessionMonitoring(): void { this.resetTimers(); this.setupActivityListeners(); } private resetTimers(): void { this.clearTimers(); // 会话超时定时器 this.timeoutTimer = setTimeout(() => { this.handleSessionTimeout(); }, this.sessionTimeout); // 会话警告定时器 this.warningTimer = setTimeout(() => { this.showSessionWarning(); }, this.warningTimeout); } private setupActivityListeners(): void { const activities = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart']; activities.forEach(event => { document.addEventListener(event, () => { this.updateLastActivity(); }, true); }); } private updateLastActivity(): void { this.lastActivity = Date.now(); this.extendSession(); } extendSession(): void { // 调用API延长会话 this.resetTimers(); } private handleSessionTimeout(): void { // 显示会话超时提示 authStore.getState().logout(); window.location.href = '/login?reason=session_timeout'; } private showSessionWarning(): void { // 显示会话即将过期警告 toast.warning('会话即将在5分钟后过期,请保存您的工作'); } } // 异常登录检测 interface SecurityMonitor { detectAnomalousLogin(ipAddress: string, userAgent: string): Promise; recordLoginAttempt(attempt: LoginAttempt): void; checkLoginFrequency(userId: string): Promise; } ``` **需要创建的文件**: - `src/services/sessionManager.ts` - 会话管理器 - `src/services/securityMonitor.ts` - 安全监控 - `src/components/auth/SessionWarning.tsx` - 会话警告组件 - `src/hooks/useSessionTimeout.ts` - 会话超时Hook #### 4. 密码修改功能 **需求**: 支持密码修改功能,具有适当的验证和安全检查 **实现计划**: ```typescript // passwordService.ts interface PasswordChangeRequest { currentPassword: string; newPassword: string; confirmPassword: string; } interface PasswordValidation { isValid: boolean; errors: string[]; strength: 'weak' | 'medium' | 'strong'; } class PasswordService { validatePassword(password: string): PasswordValidation { const errors: string[] = []; if (password.length < 8) { errors.push('密码长度至少8位'); } if (!/[A-Z]/.test(password)) { errors.push('密码必须包含大写字母'); } if (!/[a-z]/.test(password)) { errors.push('密码必须包含小写字母'); } if (!/\d/.test(password)) { errors.push('密码必须包含数字'); } if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { errors.push('密码必须包含特殊字符'); } const strength = this.calculatePasswordStrength(password); return { isValid: errors.length === 0, errors, strength }; } private calculatePasswordStrength(password: string): 'weak' | 'medium' | 'strong' { let score = 0; if (password.length >= 12) score++; if (password.length >= 8) score++; if (/[a-z]/.test(password)) score++; if (/[A-Z]/.test(password)) score++; if (/\d/.test(password)) score++; if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) score++; if (score >= 5) return 'strong'; if (score >= 3) return 'medium'; return 'weak'; } async changePassword(request: PasswordChangeRequest): Promise { // 验证当前密码 const currentPasswordValid = await this.verifyCurrentPassword(request.currentPassword); if (!currentPasswordValid) { throw new Error('当前密码不正确'); } // 验证新密码 const validation = this.validatePassword(request.newPassword); if (!validation.isValid) { throw new Error(validation.errors.join(', ')); } // 确认新密码 if (request.newPassword !== request.confirmPassword) { throw new Error('两次输入的新密码不一致'); } // 调用API修改密码 const response = await fetch('/api/auth/change-password', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${tokenStorage.getToken()}`, }, body: JSON.stringify({ currentPassword: request.currentPassword, newPassword: request.newPassword, }), }); if (!response.ok) { throw new Error('密码修改失败'); } } } // 密码修改表单组件 const PasswordChangeForm: React.FC = () => { const [formData, setFormData] = useState({ currentPassword: '', newPassword: '', confirmPassword: '', }); const [validation, setValidation] = useState(null); const [loading, setLoading] = useState(false); const handleNewPasswordChange = (value: string) => { setFormData(prev => ({ ...prev, newPassword: value })); const passwordValidation = passwordService.validatePassword(value); setValidation(passwordValidation); }; return ( 修改密码
setFormData(prev => ({ ...prev, currentPassword: e.target.value }))} required />
handleNewPasswordChange(e.target.value)} required /> {validation && ( )}
setFormData(prev => ({ ...prev, confirmPassword: e.target.value }))} required />
); }; ``` **需要创建的文件**: - `src/services/passwordService.ts` - 密码服务 - `src/components/auth/PasswordChangeForm.tsx` - 密码修改表单 - `src/components/auth/PasswordStrengthIndicator.tsx` - 密码强度指示器 - `src/hooks/usePasswordValidation.ts` - 密码验证Hook #### 5. 与原系统设计一致的登录界面 **需求**: 创建与原系统设计一致的登录界面 **实现计划**: ```typescript // LoginForm.tsx interface LoginFormProps { onSuccess?: () => void; onError?: (error: string) => void; } const LoginForm: React.FC = ({ onSuccess, onError }) => { const [loginType, setLoginType] = useState<'password' | 'phone'>('password'); const [formData, setFormData] = useState({ username: '', password: '', phone: '', phoneCode: '', captcha: '', }); const [loading, setLoading] = useState(false); const [captchaUrl, setCaptchaUrl] = useState(''); const { login } = useAuth(); // 检测管理员环境自动填充 useEffect(() => { const adminCreds = autoLoginForAdmin(); if (adminCreds) { setFormData(prev => ({ ...prev, username: adminCreds.username, password: adminCreds.password, })); } }, []); // 刷新验证码 const refreshCaptcha = () => { setCaptchaUrl(`/api/auth/captcha?t=${Date.now()}`); }; useEffect(() => { refreshCaptcha(); }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); try { if (loginType === 'password') { await login(formData.username, formData.password, formData.captcha); } else { await loginWithPhone(formData.phone, formData.phoneCode, formData.captcha); } onSuccess?.(); } catch (error) { onError?.(error instanceof Error ? error.message : '登录失败'); refreshCaptcha(); } finally { setLoading(false); } }; return (
智慧农业生产管理系统

请登录您的账户

setLoginType(value as 'password' | 'phone')}> 密码登录 手机登录
setFormData(prev => ({ ...prev, username: e.target.value }))} placeholder="请输入用户名" required />
setFormData(prev => ({ ...prev, password: e.target.value }))} placeholder="请输入密码" required />
setFormData(prev => ({ ...prev, captcha: e.target.value }))} placeholder="请输入验证码" required className="flex-1" />
setFormData(prev => ({ ...prev, phone: e.target.value }))} placeholder="请输入手机号" required />
setFormData(prev => ({ ...prev, phoneCode: e.target.value }))} placeholder="请输入短信验证码" required className="flex-1" />
{adminCreds && (

测试账号: {adminCreds.username} / {adminCreds.password}

)}
); }; ``` **需要创建的文件**: - `src/components/auth/LoginForm.tsx` - 登录表单组件 - `src/components/auth/CaptchaInput.tsx` - 验证码输入组件 - `src/components/auth/PhoneLogin.tsx` - 手机登录组件 - `src/styles/auth.css` - 认证相关样式 #### 6. "记住我"功能 **需求**: 实现"记住我"功能,用于跨浏览器会话的会话持久化 **实现计划**: ```typescript // 记住我功能实现 interface RememberMeOptions { enabled: boolean; duration: number; // 记住时长(毫秒) } class RememberMeService { private readonly storageKey = 'remember_me'; private readonly defaultDuration = 7 * 24 * 60 * 60 * 1000; // 7天 saveRememberedUser(username: string, duration: number = this.defaultDuration): void { const data = { username, timestamp: Date.now(), expiresAt: Date.now() + duration, }; localStorage.setItem(this.storageKey, JSON.stringify(data)); } getRememberedUser(): string | null { const data = localStorage.getItem(this.storageKey); if (!data) return null; try { const parsed = JSON.parse(data); if (Date.now() > parsed.expiresAt) { this.clearRememberedUser(); return null; } return parsed.username; } catch { this.clearRememberedUser(); return null; } } clearRememberedUser(): void { localStorage.removeItem(this.storageKey); } isRememberMeEnabled(): boolean { return this.getRememberedUser() !== null; } } // 在登录表单中使用 const LoginForm: React.FC = () => { const [rememberMe, setRememberMe] = useState(false); const rememberedUsername = rememberMeService.getRememberedUser(); useEffect(() => { if (rememberedUsername) { setFormData(prev => ({ ...prev, username: rememberedUsername })); setRememberMe(true); } }, [rememberedUsername]); const handleLoginSuccess = () => { if (rememberMe) { rememberMeService.saveRememberedUser(formData.username); } else { rememberMeService.clearRememberedUser(); } }; return (
{/* 表单字段 */}
); }; ``` **需要创建的文件**: - `src/services/rememberMeService.ts` - 记住我服务 - `src/components/auth/RememberMeCheckbox.tsx` - 记住我复选框 ### ✅ 集成需求实现计划 #### 4. 现有用户认证凭据继续正常工作 **实现计划**: ```typescript // 向后兼容的认证验证 class BackwardCompatibilityService { async validateLegacyCredentials(username: string, password: string): Promise { try { // 尝试使用新的认证API const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); if (response.ok) return true; // 如果新API失败,尝试旧的认证方式 return await this.tryLegacyAuth(username, password); } catch { return false; } } private async tryLegacyAuth(username: string, password: string): Promise { // 实现旧版本认证逻辑 const legacyHash = this.generateLegacyHash(password); const response = await fetch('/api/legacy/auth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, hash: legacyHash }), }); return response.ok; } private generateLegacyHash(password: string): string { // 实现旧的密码哈希算法 return btoa(password + 'legacy_salt'); } } ``` #### 5. 新认证系统遵循既定的安全模式和标准 **实现计划**: - 遵循OWASP认证安全最佳实践 - 实现密码强度验证 - 添加登录尝试限制 - 实现CSRF保护 - 使用HTTPS传输 - 安全存储令牌 #### 6. 与现有用户数据库的集成保持当前用户账户结构 **实现计划**: ```typescript // 用户数据映射 interface LegacyUser { id: number; username: string; password_hash: string; real_name: string; phone?: string; role: string; created_at: string; } interface ModernUser { id: string; username: string; realName: string; phone?: string; role: UserRole; createdAt: string; lastLoginAt?: string; isActive: boolean; } class UserDataAdapter { transformLegacyUser(legacyUser: LegacyUser): ModernUser { return { id: legacyUser.id.toString(), username: legacyUser.username, realName: legacyUser.real_name, phone: legacyUser.phone, role: this.mapLegacyRole(legacyUser.role), createdAt: legacyUser.created_at, isActive: true, }; } private mapLegacyRole(legacyRole: string): UserRole { const roleMap = { 'admin': 'admin', 'operator': 'operator', 'technician': 'technician', }; return (roleMap[legacyRole] || 'operator') as UserRole; } } ``` #### 7. 会话管理保持现有用户体验和安全级别 **实现计划**: - 保持现有的会话超时时间 - 实现平滑的令牌刷新 - 维持用户导航状态 - 保存用户偏好设置 ### ✅ 质量需求实现计划 #### 7. 认证系统被全面的安全测试覆盖 **实现计划**: ```typescript // 安全测试工具 class SecurityTestSuite { async runAuthenticationTests(): Promise { // 测试密码强度验证 await this.testPasswordStrength(); // 测试会话管理 await this.testSessionManagement(); // 测试令牌刷新 await this.testTokenRefresh(); // 测试异常登录检测 await this.testAnomalousLoginDetection(); // 测试CSRF保护 await this.testCSRFProtection(); } private async testPasswordStrength(): Promise { const weakPasswords = ['123', 'password', 'admin']; for (const password of weakPasswords) { const validation = passwordService.validatePassword(password); if (validation.isValid) { throw new Error(`弱密码验证失败: ${password}`); } } } private async testSessionManagement(): Promise { // 测试会话超时 // 测试会话刷新 // 测试并发登录 } } // 运行安全测试 const securityTests = new SecurityTestSuite(); await securityTests.runAuthenticationTests(); ``` #### 8. 认证文档更新了新的安全功能 **需要创建的文档**: - `docs/security/authentication.md` - 认证安全文档 - `docs/api/auth-endpoints.md` - 认证API文档 - `docs/user-guide/login.md` - 用户登录指南 #### 9. 验证现有用户访问功能无回归 **实现计划**: ```typescript // 回归测试套件 class AuthenticationRegressionTests { async runRegressionTests(): Promise { // 测试现有用户账户登录 await this.testExistingUserLogin(); // 测试权限系统 await this.testPermissionSystem(); // 测试导航功能 await this.testNavigationAccess(); // 测试数据访问 await this.testDataAccess(); } private async testExistingUserLogin(): Promise { const testUsers = [ { username: 'admin', password: 'admin123' }, { username: 'operator', password: 'operator123' }, ]; for (const user of testUsers) { const result = await authService.login(user.username, user.password); if (!result.success) { throw new Error(`用户 ${user.username} 登录失败`); } } } } ``` ## 📁 详细实现文件清单 ### 需要创建的核心文件 #### 1. 认证状态管理 - `src/store/authStore.ts` - Zustand认证状态管理 - `src/hooks/useAuth.ts` - 认证Hook - `src/hooks/useSessionTimeout.ts` - 会话超时Hook #### 2. 认证服务 - `src/services/authService.ts` - 认证API服务 - `src/services/sessionManager.ts` - 会话管理器 - `src/services/securityMonitor.ts` - 安全监控服务 - `src/services/passwordService.ts` - 密码服务 - `src/services/rememberMeService.ts` - 记住我服务 - `src/services/httpClient.ts` - HTTP客户端 #### 3. 认证组件 - `src/components/auth/LoginForm.tsx` - 登录表单 - `src/components/auth/PasswordChangeForm.tsx` - 密码修改表单 - `src/components/auth/PasswordStrengthIndicator.tsx` - 密码强度指示器 - `src/components/auth/SessionWarning.tsx` - 会话警告组件 - `src/components/auth/CaptchaInput.tsx` - 验证码输入组件 - `src/components/auth/PhoneLogin.tsx` - 手机登录组件 #### 4. 工具和配置 - `src/utils/tokenStorage.ts` - 令牌存储工具 - `src/middleware/authInterceptor.ts` - 认证拦截器 - `src/types/auth.ts` - 认证相关类型定义 - `src/config/auth.ts` - 认证配置 #### 5. 测试文件 - `src/tests/auth.test.ts` - 认证功能测试 - `src/tests/security.test.ts` - 安全功能测试 - `src/tests/regression.test.ts` - 回归测试 #### 6. 文档 - `docs/security/authentication.md` - 认证安全文档 - `docs/api/auth-endpoints.md` - 认证API文档 - `docs/user-guide/login.md` - 用户登录指南 ## 🚀 实施步骤 ### 阶段1: 基础认证架构 (60分钟) 1. 创建认证状态管理 (authStore.ts) 2. 实现基础认证服务 (authService.ts) 3. 创建令牌存储工具 (tokenStorage.ts) 4. 设置HTTP客户端和拦截器 ### 阶段2: 登录界面开发 (45分钟) 1. 创建登录表单组件 (LoginForm.tsx) 2. 实现验证码功能 (CaptchaInput.tsx) 3. 添加手机登录支持 (PhoneLogin.tsx) 4. 实现记住我功能 ### 阶段3: 会话管理和安全 (60分钟) 1. 实现会话管理器 (sessionManager.ts) 2. 添加安全监控 (securityMonitor.ts) 3. 实现令牌自动刷新 4. 创建会话超时警告 ### 阶段4: 密码管理功能 (45分钟) 1. 创建密码修改表单 (PasswordChangeForm.tsx) 2. 实现密码强度验证 (PasswordStrengthIndicator.tsx) 3. 添加密码安全策略 4. 创建密码服务 (passwordService.ts) ### 阶段5: 集成和测试 (60分钟) 1. 集成所有认证组件 2. 实现向后兼容性 3. 运行安全测试 4. 执行回归测试 5. 更新文档 ## ✅ 预期成果 ### 完成后系统将具备 1. ✅ 现代化的JWT认证系统 2. ✅ 安全的会话管理和令牌刷新 3. ✅ 多种登录方式支持 4. ✅ 密码强度验证和修改功能 5. ✅ 异常登录检测和防护 6. ✅ 完善的用户体验和界面 7. ✅ 全面的安全测试覆盖 ### 验收标准完成情况 - [ ] 功能需求1: 用户名/密码登录功能 - [ ] 功能需求2: JWT令牌自动刷新和管理 - [ ] 功能需求3: 会话超时和异常登录检测 - [ ] 功能需求4: 密码修改功能 - [ ] 功能需求5: 与原系统设计一致的登录界面 - [ ] 功能需求6: "记住我"功能 - [ ] 集成需求4: 现有用户认证凭据兼容 - [ ] 集成需求5: 遵循安全模式和标准 - [ ] 集成需求6: 用户数据库集成 - [ ] 集成需求7: 会话管理保持用户体验 - [ ] 质量需求7: 安全测试覆盖 - [ ] 质量需求8: 认证文档更新 - [ ] 质量需求9: 无回归验证 --- ## 🛡️ 安全考虑 ### 实现的安全措施 1. **密码安全**: 强密码策略、加盐哈希存储 2. **令牌安全**: JWT令牌、自动刷新机制 3. **会话安全**: 超时管理、异常检测 4. **传输安全**: HTTPS、CSRF保护 5. **输入验证**: 前后端双重验证 6. **访问控制**: 基于角色的权限管理 ### 安全测试 - 密码强度测试 - 会话管理测试 - 令牌刷新测试 - 异常登录检测测试 - CSRF保护测试 - 权限控制测试 --- *此实现计划遵循用户故事1.3的所有验收标准,确保系统具备现代化、安全的认证功能。*