Files
smart-crop-ui/docs/devAchievementPlan/story-achieve-1-3-认证系统现代化.md

30 KiB
Raw Permalink Blame History

用户故事1.3实现计划:认证系统现代化

📋 实现目标

作为 系统用户,我想要 在新系统中安全便捷地登录和管理我的账户,以便 我能够正常访问配置管理功能。

🎯 验收标准对照与实现计划

功能需求实现计划

1. 用户名/密码登录功能

需求: 实现用户名/密码登录功能,支持管理员自动登录功能

当前状态分析:

  • 登录页面基础结构存在
  • 表单组件已创建
  • ⚠️ 缺少JWT令牌管理
  • ⚠️ 缺少会话管理
  • ⚠️ 缺少管理员自动登录功能

实现计划:

// 1. 创建认证Context
interface AuthContextType {
  user: User | null;
  token: string | null;
  login: (username: string, password: string) => Promise<void>;
  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 令牌自动刷新和管理机制,用于安全的会话处理

实现计划:

// 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<string> | null = null;

  async refreshToken(): Promise<string> {
    // 防止并发刷新
    if (this.refreshPromise) {
      return this.refreshPromise;
    }

    this.refreshPromise = this.doRefreshToken();
    return this.refreshPromise.finally(() => {
      this.refreshPromise = null;
    });
  }

  private async doRefreshToken(): Promise<string> {
    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. 会话超时和异常登录检测

需求: 实现会话超时和异常登录检测,增强安全性

实现计划:

// 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<boolean>;
  recordLoginAttempt(attempt: LoginAttempt): void;
  checkLoginFrequency(userId: string): Promise<boolean>;
}

需要创建的文件:

  • src/services/sessionManager.ts - 会话管理器
  • src/services/securityMonitor.ts - 安全监控
  • src/components/auth/SessionWarning.tsx - 会话警告组件
  • src/hooks/useSessionTimeout.ts - 会话超时Hook

4. 密码修改功能

需求: 支持密码修改功能,具有适当的验证和安全检查

实现计划:

// 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<void> {
    // 验证当前密码
    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<PasswordChangeRequest>({
    currentPassword: '',
    newPassword: '',
    confirmPassword: '',
  });

  const [validation, setValidation] = useState<PasswordValidation | null>(null);
  const [loading, setLoading] = useState(false);

  const handleNewPasswordChange = (value: string) => {
    setFormData(prev => ({ ...prev, newPassword: value }));
    const passwordValidation = passwordService.validatePassword(value);
    setValidation(passwordValidation);
  };

  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <CardTitle>修改密码</CardTitle>
      </CardHeader>
      <CardContent>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div>
            <Label htmlFor="currentPassword">当前密码</Label>
            <Input
              id="currentPassword"
              type="password"
              value={formData.currentPassword}
              onChange={(e) => setFormData(prev => ({
                ...prev,
                currentPassword: e.target.value
              }))}
              required
            />
          </div>

          <div>
            <Label htmlFor="newPassword">新密码</Label>
            <Input
              id="newPassword"
              type="password"
              value={formData.newPassword}
              onChange={(e) => handleNewPasswordChange(e.target.value)}
              required
            />
            {validation && (
              <PasswordStrengthIndicator validation={validation} />
            )}
          </div>

          <div>
            <Label htmlFor="confirmPassword">确认新密码</Label>
            <Input
              id="confirmPassword"
              type="password"
              value={formData.confirmPassword}
              onChange={(e) => setFormData(prev => ({
                ...prev,
                confirmPassword: e.target.value
              }))}
              required
            />
          </div>

          <Button type="submit" disabled={loading} className="w-full">
            {loading ? '修改中...' : '修改密码'}
          </Button>
        </form>
      </CardContent>
    </Card>
  );
};

需要创建的文件:

  • src/services/passwordService.ts - 密码服务
  • src/components/auth/PasswordChangeForm.tsx - 密码修改表单
  • src/components/auth/PasswordStrengthIndicator.tsx - 密码强度指示器
  • src/hooks/usePasswordValidation.ts - 密码验证Hook

5. 与原系统设计一致的登录界面

需求: 创建与原系统设计一致的登录界面

实现计划:

// LoginForm.tsx
interface LoginFormProps {
  onSuccess?: () => void;
  onError?: (error: string) => void;
}

const LoginForm: React.FC<LoginFormProps> = ({ 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 (
    <div className="min-h-screen bg-gradient-to-br from-green-50 via-blue-50 to-cyan-50 flex items-center justify-center p-4">
      <Card className="w-full max-w-md">
        <CardHeader className="text-center">
          <div className="w-16 h-16 bg-green-600 rounded-full mx-auto mb-4 flex items-center justify-center">
            <Tractor className="w-8 h-8 text-white" />
          </div>
          <CardTitle className="text-2xl text-green-900">
            智慧农业生产管理系统
          </CardTitle>
          <p className="text-muted-foreground">
            请登录您的账户
          </p>
        </CardHeader>

        <CardContent>
          <Tabs value={loginType} onValueChange={(value) => setLoginType(value as 'password' | 'phone')}>
            <TabsList className="grid w-full grid-cols-2">
              <TabsTrigger value="password">密码登录</TabsTrigger>
              <TabsTrigger value="phone">手机登录</TabsTrigger>
            </TabsList>

            <TabsContent value="password">
              <form onSubmit={handleSubmit} className="space-y-4">
                <div>
                  <Label htmlFor="username">用户名</Label>
                  <Input
                    id="username"
                    type="text"
                    value={formData.username}
                    onChange={(e) => setFormData(prev => ({
                      ...prev,
                      username: e.target.value
                    }))}
                    placeholder="请输入用户名"
                    required
                  />
                </div>

                <div>
                  <Label htmlFor="password">密码</Label>
                  <Input
                    id="password"
                    type="password"
                    value={formData.password}
                    onChange={(e) => setFormData(prev => ({
                      ...prev,
                      password: e.target.value
                    }))}
                    placeholder="请输入密码"
                    required
                  />
                </div>

                <div>
                  <Label htmlFor="captcha">验证码</Label>
                  <div className="flex space-x-2">
                    <Input
                      id="captcha"
                      type="text"
                      value={formData.captcha}
                      onChange={(e) => setFormData(prev => ({
                        ...prev,
                        captcha: e.target.value
                      }))}
                      placeholder="请输入验证码"
                      required
                      className="flex-1"
                    />
                    <Button
                      type="button"
                      variant="outline"
                      onClick={refreshCaptcha}
                      className="px-3"
                    >
                      <img
                        src={captchaUrl}
                        alt="验证码"
                        className="h-10 w-20"
                        onError={(e) => {
                          (e.target as HTMLImageElement).style.display = 'none';
                        }}
                      />
                    </Button>
                  </div>
                </div>

                <div className="flex items-center justify-between">
                  <div className="flex items-center space-x-2">
                    <Checkbox id="remember" />
                    <Label htmlFor="remember">记住登录状态</Label>
                  </div>
                  <Button variant="link" className="p-0 h-auto">
                    忘记密码?
                  </Button>
                </div>

                <Button type="submit" disabled={loading} className="w-full">
                  {loading ? '登录中...' : '登录'}
                </Button>
              </form>
            </TabsContent>

            <TabsContent value="phone">
              <form onSubmit={handleSubmit} className="space-y-4">
                <div>
                  <Label htmlFor="phone">手机号</Label>
                  <Input
                    id="phone"
                    type="tel"
                    value={formData.phone}
                    onChange={(e) => setFormData(prev => ({
                      ...prev,
                      phone: e.target.value
                    }))}
                    placeholder="请输入手机号"
                    required
                  />
                </div>

                <div>
                  <Label htmlFor="phoneCode">短信验证码</Label>
                  <div className="flex space-x-2">
                    <Input
                      id="phoneCode"
                      type="text"
                      value={formData.phoneCode}
                      onChange={(e) => setFormData(prev => ({
                        ...prev,
                        phoneCode: e.target.value
                      }))}
                      placeholder="请输入短信验证码"
                      required
                      className="flex-1"
                    />
                    <Button type="button" variant="outline">
                      获取验证码
                    </Button>
                  </div>
                </div>

                <Button type="submit" disabled={loading} className="w-full">
                  {loading ? '登录中...' : '登录'}
                </Button>
              </form>
            </TabsContent>
          </Tabs>

          {adminCreds && (
            <div className="mt-4 p-3 bg-green-50 border border-green-200 rounded-md">
              <p className="text-sm text-green-700">
                <strong>测试账号:</strong> {adminCreds.username} / {adminCreds.password}
              </p>
            </div>
          )}
        </CardContent>
      </Card>
    </div>
  );
};

需要创建的文件:

  • src/components/auth/LoginForm.tsx - 登录表单组件
  • src/components/auth/CaptchaInput.tsx - 验证码输入组件
  • src/components/auth/PhoneLogin.tsx - 手机登录组件
  • src/styles/auth.css - 认证相关样式

6. "记住我"功能

需求: 实现"记住我"功能,用于跨浏览器会话的会话持久化

实现计划:

// 记住我功能实现
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 (
    <form onSubmit={handleSubmit}>
      {/* 表单字段 */}

      <div className="flex items-center space-x-2">
        <Checkbox
          id="remember"
          checked={rememberMe}
          onCheckedChange={setRememberMe}
        />
        <Label htmlFor="remember">记住登录状态</Label>
      </div>

      <Button type="submit">登录</Button>
    </form>
  );
};

需要创建的文件:

  • src/services/rememberMeService.ts - 记住我服务
  • src/components/auth/RememberMeCheckbox.tsx - 记住我复选框

集成需求实现计划

4. 现有用户认证凭据继续正常工作

实现计划:

// 向后兼容的认证验证
class BackwardCompatibilityService {
  async validateLegacyCredentials(username: string, password: string): Promise<boolean> {
    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<boolean> {
    // 实现旧版本认证逻辑
    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. 与现有用户数据库的集成保持当前用户账户结构

实现计划:

// 用户数据映射
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. 认证系统被全面的安全测试覆盖

实现计划:

// 安全测试工具
class SecurityTestSuite {
  async runAuthenticationTests(): Promise<void> {
    // 测试密码强度验证
    await this.testPasswordStrength();

    // 测试会话管理
    await this.testSessionManagement();

    // 测试令牌刷新
    await this.testTokenRefresh();

    // 测试异常登录检测
    await this.testAnomalousLoginDetection();

    // 测试CSRF保护
    await this.testCSRFProtection();
  }

  private async testPasswordStrength(): Promise<void> {
    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<void> {
    // 测试会话超时
    // 测试会话刷新
    // 测试并发登录
  }
}

// 运行安全测试
const securityTests = new SecurityTestSuite();
await securityTests.runAuthenticationTests();

8. 认证文档更新了新的安全功能

需要创建的文档:

  • docs/security/authentication.md - 认证安全文档
  • docs/api/auth-endpoints.md - 认证API文档
  • docs/user-guide/login.md - 用户登录指南

9. 验证现有用户访问功能无回归

实现计划:

// 回归测试套件
class AuthenticationRegressionTests {
  async runRegressionTests(): Promise<void> {
    // 测试现有用户账户登录
    await this.testExistingUserLogin();

    // 测试权限系统
    await this.testPermissionSystem();

    // 测试导航功能
    await this.testNavigationAccess();

    // 测试数据访问
    await this.testDataAccess();
  }

  private async testExistingUserLogin(): Promise<void> {
    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的所有验收标准,确保系统具备现代化、安全的认证功能。