Auth.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. <?php
  2. namespace BCJD;
  3. /**
  4. * Auth - 认证管理(登录/CSRF/Session 安全)
  5. *
  6. * 安全特性:
  7. * - CSRF Token 自动生成与验证
  8. * - Session 固定会话防护(regenerate_id)
  9. * - Session 超时机制(30分钟无操作自动过期)
  10. * - 登录失败延迟(防暴力破解)
  11. * - 安全的 Cookie 参数
  12. * - bcrypt 密码哈希验证
  13. */
  14. class Auth
  15. {
  16. /**
  17. * 初始化 Session 安全配置
  18. */
  19. public static function initSession(): void
  20. {
  21. if (session_status() === PHP_SESSION_ACTIVE) {
  22. return;
  23. }
  24. // 仅通过 Cookie 传递 Session ID
  25. ini_set('session.use_only_cookies', '1');
  26. ini_set('session.use_strict_mode', '1');
  27. ini_set('session.cookie_httponly', '1');
  28. ini_set('session.cookie_samesite', 'Lax');
  29. ini_set('session.cookie_secure', '0'); // 如果使用 HTTPS 可改为 1
  30. ini_set('session.gc_maxlifetime', (string)SESSION_TIMEOUT);
  31. session_start();
  32. // Session 超时检查
  33. if (!empty($_SESSION['_last_activity'])) {
  34. if (time() - $_SESSION['_last_activity'] > SESSION_TIMEOUT) {
  35. self::destroy();
  36. session_start();
  37. }
  38. }
  39. $_SESSION['_last_activity'] = time();
  40. }
  41. /**
  42. * 校验密码
  43. */
  44. public static function verifyPassword(string $password): bool
  45. {
  46. // 登录失败延迟(防止暴力破解)
  47. usleep(LOGIN_DELAY * 1000);
  48. return password_verify($password, SITE_PASSWORD_HASH);
  49. }
  50. /**
  51. * 执行登录
  52. */
  53. public static function login(): void
  54. {
  55. self::initSession();
  56. $_SESSION['logged_in'] = true;
  57. $_SESSION['_login_time'] = time();
  58. session_regenerate_id(true);
  59. }
  60. /**
  61. * 执行登出
  62. */
  63. public static function logout(): void
  64. {
  65. self::initSession();
  66. self::destroy();
  67. }
  68. /**
  69. * 检查是否已登录
  70. */
  71. public static function isLoggedIn(): bool
  72. {
  73. self::initSession();
  74. return !empty($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;
  75. }
  76. /**
  77. * 要求登录,未登录则返回 401
  78. */
  79. public static function requireLogin(): void
  80. {
  81. if (!self::isLoggedIn()) {
  82. Response::error('请先登录', 401);
  83. }
  84. }
  85. /**
  86. * 生成 CSRF Token 并存入 Session
  87. */
  88. public static function generateCsrfToken(): string
  89. {
  90. self::initSession();
  91. $token = bin2hex(random_bytes(32));
  92. $_SESSION['csrf_token'] = $token;
  93. return $token;
  94. }
  95. /**
  96. * 验证 CSRF Token
  97. */
  98. public static function validateCsrfToken(?string $token): bool
  99. {
  100. self::initSession();
  101. if (empty($_SESSION['csrf_token']) || empty($token)) {
  102. return false;
  103. }
  104. return hash_equals($_SESSION['csrf_token'], $token);
  105. }
  106. /**
  107. * 获取 CSRF Token(用于前端表单)
  108. */
  109. public static function getCsrfToken(): string
  110. {
  111. self::initSession();
  112. if (empty($_SESSION['csrf_token'])) {
  113. return self::generateCsrfToken();
  114. }
  115. return $_SESSION['csrf_token'];
  116. }
  117. /**
  118. * 生成 CSRF 隐藏字段 HTML
  119. */
  120. public static function csrfField(): string
  121. {
  122. return '<input type="hidden" name="csrf_token" value="' . self::getCsrfToken() . '">';
  123. }
  124. /**
  125. * 添加安全响应头
  126. */
  127. public static function sendSecurityHeaders(): void
  128. {
  129. header('X-Frame-Options: DENY');
  130. header('X-Content-Type-Options: nosniff');
  131. header('X-XSS-Protection: 1; mode=block');
  132. header('Referrer-Policy: strict-origin-when-cross-origin');
  133. }
  134. /**
  135. * 销毁会话
  136. */
  137. private static function destroy(): void
  138. {
  139. $_SESSION = [];
  140. if (ini_get('session.use_cookies')) {
  141. $params = session_get_cookie_params();
  142. setcookie(
  143. session_name(),
  144. '',
  145. time() - 42000,
  146. $params['path'],
  147. $params['domain'],
  148. $params['secure'],
  149. $params['httponly']
  150. );
  151. }
  152. session_destroy();
  153. }
  154. }