PHP项目异常登录检测提醒

wen PHP项目 4

本文目录导读:

PHP项目异常登录检测提醒

  1. 数据库设计
  2. 异常检测核心类
  3. 警报通知系统
  4. 登录处理集成
  5. 用户安全面板
  6. 使用示例
  7. 前端提醒脚本
  8. API端点

我来为您设计一个PHP项目异常登录检测提醒系统,这个系统可以检测和提醒用户异常登录行为,提高账户安全性。

数据库设计

-- 用户登录日志表
CREATE TABLE user_login_logs (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    login_time DATETIME NOT NULL,
    ip_address VARCHAR(45) NOT NULL,
    user_agent TEXT,
    login_status ENUM('success', 'failed', 'suspicious') DEFAULT 'success',
    location VARCHAR(255),
    risk_score DECIMAL(3,2) DEFAULT 0.00,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user_id (user_id),
    INDEX idx_login_time (login_time),
    INDEX idx_ip_address (ip_address)
);
-- 用户安全设置表
CREATE TABLE user_security_settings (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL UNIQUE,
    trusted_devices JSON,
    trusted_locations JSON,
    login_notification BOOLEAN DEFAULT TRUE,
    suspicious_login_alert BOOLEAN DEFAULT TRUE,
    max_failed_attempts INT DEFAULT 5,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 异常登录警报表
CREATE TABLE login_alerts (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    alert_type VARCHAR(50) NOT NULL,
    alert_message TEXT,
    ip_address VARCHAR(45),
    detected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_read BOOLEAN DEFAULT FALSE,
    action_taken VARCHAR(255),
    INDEX idx_user_id (user_id),
    INDEX idx_detected_at (detected_at)
);

异常检测核心类

<?php
// AnomalyDetection.php
class AnomalyDetection {
    private $db;
    private $config;
    public function __construct($db, $config = []) {
        $this->db = $db;
        $this->config = array_merge([
            'time_window' => 3600, // 1小时的检测窗口
            'max_login_attempts' => 10,
            'geo_change_alert' => true,
            'device_change_alert' => true,
            'time_pattern_alert' => true
        ], $config);
    }
    /**
     * 检测登录异常
     */
    public function detectAnomaly($userId, $currentIp, $userAgent, $loginTime = null) {
        $loginTime = $loginTime ?? date('Y-m-d H:i:s');
        $anomalies = [];
        // 1. 检测地理位置变化
        if ($this->config['geo_change_alert']) {
            $geoAnomaly = $this->detectGeoChange($userId, $currentIp);
            if ($geoAnomaly) {
                $anomalies[] = $geoAnomaly;
            }
        }
        // 2. 检测设备变化
        if ($this->config['device_change_alert']) {
            $deviceAnomaly = $this->detectDeviceChange($userId, $userAgent);
            if ($deviceAnomaly) {
                $anomalies[] = $deviceAnomaly;
            }
        }
        // 3. 检测登录频率异常
        $frequencyAnomaly = $this->detectFrequencyAnomaly($userId, $loginTime);
        if ($frequencyAnomaly) {
            $anomalies[] = $frequencyAnomaly;
        }
        // 4. 检测异常时间段
        if ($this->config['time_pattern_alert']) {
            $timeAnomaly = $this->detectTimePatternAnomaly($userId, $loginTime);
            if ($timeAnomaly) {
                $anomalies[] = $timeAnomaly;
            }
        }
        // 计算风险评分
        $riskScore = $this->calculateRiskScore($anomalies);
        return [
            'is_anomaly' => !empty($anomalies),
            'anomalies' => $anomalies,
            'risk_score' => $riskScore,
            'total_checks' => 4,
            'failed_checks' => count($anomalies)
        ];
    }
    /**
     * 检测地理位置变化
     */
    private function detectGeoChange($userId, $currentIp) {
        $lastLogin = $this->getLastLogin($userId);
        if (!$lastLogin) {
            return null;
        }
        $currentLocation = $this->getLocationFromIp($currentIp);
        $lastLocation = $this->getLocationFromIp($lastLogin['ip_address']);
        if ($currentLocation && $lastLocation) {
            $distance = $this->calculateDistance(
                $currentLocation['lat'], $currentLocation['lon'],
                $lastLocation['lat'], $lastLocation['lon']
            );
            // 如果距离超过1000公里且在短时间内,标记为异常
            if ($distance > 1000) {
                $timeDiff = strtotime(date('Y-m-d H:i:s')) - strtotime($lastLogin['login_time']);
                if ($timeDiff < 7200) { // 2小时内跨越1000公里
                    return [
                        'type' => 'geo_change',
                        'severity' => 'high',
                        'message' => "异常地理位置变化: 从 {$lastLocation['city']} 到 {$currentLocation['city']}",
                        'distance' => $distance . 'km',
                        'time_diff' => $timeDiff . '秒'
                    ];
                }
            }
        }
        return null;
    }
    /**
     * 检测设备变化
     */
    private function detectDeviceChange($userId, $currentUserAgent) {
        $lastLogins = $this->getLastLogins($userId, 5);
        if (empty($lastLogins)) {
            return null;
        }
        $deviceFingerprint = $this->generateDeviceFingerprint($currentUserAgent);
        $knownDevices = [];
        foreach ($lastLogins as $login) {
            $fingerprint = $this->generateDeviceFingerprint($login['user_agent']);
            $knownDevices[$fingerprint] = true;
        }
        if (!isset($knownDevices[$deviceFingerprint])) {
            return [
                'type' => 'device_change',
                'severity' => 'medium',
                'message' => '从未知设备登录',
                'device_fingerprint' => $deviceFingerprint
            ];
        }
        return null;
    }
    /**
     * 检测登录频率异常
     */
    private function detectFrequencyAnomaly($userId, $loginTime) {
        $windowStart = date('Y-m-d H:i:s', strtotime($loginTime) - $this->config['time_window']);
        $stmt = $this->db->prepare("
            SELECT COUNT(*) as count 
            FROM user_login_logs 
            WHERE user_id = ? 
            AND login_time >= ? 
            AND login_time <= ?
        ");
        $stmt->execute([$userId, $windowStart, $loginTime]);
        $result = $stmt->fetch();
        if ($result['count'] > $this->config['max_login_attempts']) {
            return [
                'type' => 'frequency',
                'severity' => 'high',
                'message' => "短时间内多次登录: {$result['count']} 次在 " . ($this->config['time_window'] / 3600) . " 小时内",
                'count' => $result['count'],
                'time_window' => $this->config['time_window']
            ];
        }
        return null;
    }
    /**
     * 检测异常时间段
     */
    private function detectTimePatternAnomaly($userId, $loginTime) {
        $hour = date('H', strtotime($loginTime));
        $dayOfWeek = date('w', strtotime($loginTime));
        // 获取用户通常登录时间段
        $stmt = $this->db->prepare("
            SELECT HOUR(login_time) as hour, 
                   DAYOFWEEK(login_time) as day_of_week,
                   COUNT(*) as count
            FROM user_login_logs 
            WHERE user_id = ? 
            AND login_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
            GROUP BY HOUR(login_time), DAYOFWEEK(login_time)
            ORDER BY count DESC
            LIMIT 10
        ");
        $stmt->execute([$userId]);
        $patterns = $stmt->fetchAll();
        if (!empty($patterns)) {
            $usualHours = array_column($patterns, 'hour');
            $usualDays = array_column($patterns, 'day_of_week');
            if (!in_array($hour, $usualHours) && !in_array($dayOfWeek, $usualDays)) {
                return [
                    'type' => 'time_pattern',
                    'severity' => 'low',
                    'message' => "在异常时间段登录: {$hour}:00, 星期" . $dayOfWeek,
                    'current_hour' => $hour,
                    'current_day' => $dayOfWeek
                ];
            }
        }
        return null;
    }
    /**
     * 计算风险评分
     */
    private function calculateRiskScore($anomalies) {
        $score = 0;
        foreach ($anomalies as $anomaly) {
            switch ($anomaly['severity']) {
                case 'high':
                    $score += 0.4;
                    break;
                case 'medium':
                    $score += 0.25;
                    break;
                case 'low':
                    $score += 0.1;
                    break;
            }
        }
        return min($score, 1.0);
    }
    /**
     * 获取用户最后登录记录
     */
    private function getLastLogin($userId) {
        $stmt = $this->db->prepare("
            SELECT * FROM user_login_logs 
            WHERE user_id = ? 
            AND login_status = 'success'
            ORDER BY login_time DESC 
            LIMIT 1
        ");
        $stmt->execute([$userId]);
        return $stmt->fetch();
    }
    /**
     * 获取用户最近登录记录
     */
    private function getLastLogins($userId, $limit = 5) {
        $stmt = $this->db->prepare("
            SELECT * FROM user_login_logs 
            WHERE user_id = ? 
            ORDER BY login_time DESC 
            LIMIT ?
        ");
        $stmt->execute([$userId, $limit]);
        return $stmt->fetchAll();
    }
    /**
     * 从IP获取地理位置
     */
    private function getLocationFromIp($ip) {
        // 可以使用第三方API,如ip-api.com
        $response = @file_get_contents("http://ip-api.com/json/{$ip}");
        if ($response) {
            $data = json_decode($response, true);
            if ($data['status'] === 'success') {
                return [
                    'lat' => $data['lat'],
                    'lon' => $data['lon'],
                    'city' => $data['city'],
                    'country' => $data['country']
                ];
            }
        }
        return null;
    }
    /**
     * 计算两点间的距离(Haversine公式)
     */
    private function calculateDistance($lat1, $lon1, $lat2, $lon2) {
        $radius = 6371; // 地球半径(km)
        $dlat = deg2rad($lat2 - $lat1);
        $dlon = deg2rad($lon2 - $lon1);
        $a = sin($dlat/2) * sin($dlat/2) + 
             cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * 
             sin($dlon/2) * sin($dlon/2);
        $c = 2 * atan2(sqrt($a), sqrt(1-$a));
        return $radius * $c;
    }
    /**
     * 生成设备指纹
     */
    private function generateDeviceFingerprint($userAgent) {
        return md5($userAgent);
    }
}

警报通知系统

<?php
// AlertNotification.php
class AlertNotification {
    private $db;
    private $emailSender;
    public function __construct($db, $emailSender) {
        $this->db = $db;
        $this->emailSender = $emailSender;
    }
    /**
     * 发送异常登录警报
     */
    public function sendAlert($userId, $anomalyData) {
        // 获取用户信息
        $user = $this->getUserInfo($userId);
        $securitySettings = $this->getSecuritySettings($userId);
        if (!$securitySettings['suspicious_login_alert']) {
            return false;
        }
        // 记录警报
        $alertId = $this->logAlert($userId, $anomalyData);
        // 发送通知
        $notificationsSent = [];
        // 1. 邮件通知
        $emailSent = $this->sendEmailAlert($user, $anomalyData);
        if ($emailSent) {
            $notificationsSent[] = 'email';
        }
        // 2. 短信通知(如果配置了)
        if (!empty($user['phone'])) {
            $smsSent = $this->sendSmsAlert($user['phone'], $anomalyData);
            if ($smsSent) {
                $notificationsSent[] = 'sms';
            }
        }
        // 3. 推送通知(如果系统支持)
        if (!empty($user['push_token'])) {
            $pushSent = $this->sendPushNotification($user['push_token'], $anomalyData);
            if ($pushSent) {
                $notificationsSent[] = 'push';
            }
        }
        return [
            'alert_id' => $alertId,
            'notifications_sent' => $notificationsSent
        ];
    }
    /**
     * 发送邮件警报
     */
    private function sendEmailAlert($user, $anomalyData) {
        $subject = "⚠️ 安全警报: 检测到异常登录活动";
        $message = "
        <html>
        <head>
            <style>
                body { font-family: Arial, sans-serif; }
                .alert-box { 
                    background-color: #fff3cd;
                    border: 1px solid #ffc107;
                    padding: 15px;
                    margin: 20px 0;
                    border-radius: 5px;
                }
                .high-risk { 
                    background-color: #f8d7da;
                    border-color: #f5c6cb;
                }
                .info {
                    margin: 10px 0;
                    padding: 10px;
                    background-color: #f8f9fa;
                    border-radius: 5px;
                }
            </style>
        </head>
        <body>
            <h2>异常登录检测警报</h2>
            <div class='alert-box'>
                <h3>检测到以下异常:</h3>
                <ul>
        ";
        foreach ($anomalyData['anomalies'] as $anomaly) {
            $message .= "<li><strong>{$anomaly['type']}:</strong> {$anomaly['message']}</li>";
        }
        $message .= "
                </ul>
            </div>
            <div class='info'>
                <p><strong>风险评分:</strong> " . ($anomalyData['risk_score'] * 100) . "%</p>
                <p><strong>登录时间:</strong> " . date('Y-m-d H:i:s') . "</p>
                <p><strong>IP地址:</strong> {$this->getClientIp()}</p>
            </div>
            <div class='actions'>
                <h3>建议操作:</h3>
                <ul>
                    <li>如果是您本人操作,请忽略此邮件</li>
                    <li>如果不是您操作的,请立即修改密码</li>
                    <li>检查账户安全设置,启用双因素认证</li>
                    <li>查看最近的登录活动</li>
                </ul>
                <p>
                    <a href='https://yourdomain.com/security/check' 
                       style='background-color: #007bff; color: white; padding: 10px 20px; 
                              text-decoration: none; border-radius: 5px;'>
                        查看安全设置
                    </a>
                </p>
            </div>
        </body>
        </html>
        ";
        return $this->emailSender->sendHtml(
            $user['email'],
            $subject,
            $message
        );
    }
    /**
     * 发送短信警报
     */
    private function sendSmsAlert($phone, $anomalyData) {
        $riskLevel = $anomalyData['risk_score'] > 0.5 ? '高风险' : '中等风险';
        $anomalyTypes = array_map(function($a) { return $a['type']; }, $anomalyData['anomalies']);
        $message = "[安全警报] 您的账户检测到{$riskLevel}登录异常: " 
                  . implode(', ', $anomalyTypes) 
                  . ",IP: {$this->getClientIp()} 时间: " 
                  . date('H:i:s');
        // 使用短信服务发送
        return $this->sendSms($phone, $message);
    }
    /**
     * 记录警报到数据库
     */
    private function logAlert($userId, $anomalyData) {
        $stmt = $this->db->prepare("
            INSERT INTO login_alerts 
            (user_id, alert_type, alert_message, ip_address)
            VALUES (?, ?, ?, ?)
        ");
        $stmt->execute([
            $userId,
            'suspicious_login',
            json_encode($anomalyData),
            $this->getClientIp()
        ]);
        return $this->db->lastInsertId();
    }
    /**
     * 获取用户信息
     */
    private function getUserInfo($userId) {
        $stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
        $stmt->execute([$userId]);
        return $stmt->fetch();
    }
    /**
     * 获取用户安全设置
     */
    private function getSecuritySettings($userId) {
        $stmt = $this->db->prepare("
            SELECT * FROM user_security_settings WHERE user_id = ?
        ");
        $stmt->execute([$userId]);
        return $stmt->fetch() ?: ['suspicious_login_alert' => true];
    }
    /**
     * 获取客户端IP
     */
    private function getClientIp() {
        $ipAddress = '';
        if (isset($_SERVER['HTTP_CLIENT_IP']))
            $ipAddress = $_SERVER['HTTP_CLIENT_IP'];
        else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
            $ipAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
        else if(isset($_SERVER['HTTP_X_FORWARDED']))
            $ipAddress = $_SERVER['HTTP_X_FORWARDED'];
        else if(isset($_SERVER['HTTP_FORWARDED_FOR']))
            $ipAddress = $_SERVER['HTTP_FORWARDED_FOR'];
        else if(isset($_SERVER['HTTP_FORWARDED']))
            $ipAddress = $_SERVER['HTTP_FORWARDED'];
        else if(isset($_SERVER['REMOTE_ADDR']))
            $ipAddress = $_SERVER['REMOTE_ADDR'];
        else
            $ipAddress = 'UNKNOWN';
        return $ipAddress;
    }
}

登录处理集成

<?php
// LoginProcessor.php
class LoginProcessor {
    private $db;
    private $anomalyDetection;
    private $alertNotification;
    public function __construct($db) {
        $this->db = $db;
        $this->anomalyDetection = new AnomalyDetection($db);
        $this->alertNotification = new AlertNotification($db, new EmailSender());
    }
    /**
     * 处理登录
     */
    public function processLogin($username, $password) {
        // 验证用户凭据
        $user = $this->authenticateUser($username, $password);
        if (!$user) {
            $this->logFailedLogin($username);
            return ['success' => false, 'message' => '用户名或密码错误'];
        }
        // 检测登录异常
        $clientIp = $this->getClientIp();
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        $anomalyResult = $this->anomalyDetection->detectAnomaly(
            $user['id'], 
            $clientIp, 
            $userAgent
        );
        // 记录登录日志
        $this->logLogin($user['id'], $clientIp, $userAgent, 
                       $anomalyResult['is_anomaly'] ? 'suspicious' : 'success',
                       $anomalyResult['risk_score']);
        // 如果检测到异常,发送警报
        if ($anomalyResult['is_anomaly']) {
            $alertResult = $this->alertNotification->sendAlert(
                $user['id'], 
                $anomalyResult
            );
            // 根据风险等级决定是否允许登录
            if ($anomalyResult['risk_score'] > 0.7) {
                // 高风险:要求额外验证
                return [
                    'success' => false,
                    'require_verification' => true,
                    'message' => '检测到高风险登录,需要进行额外验证',
                    'anomaly' => $anomalyResult,
                    'alert' => $alertResult
                ];
            }
        }
        // 安全登录
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['login_time'] = time();
        $_SESSION['login_token'] = bin2hex(random_bytes(32));
        return [
            'success' => true,
            'user' => $user,
            'anomaly' => $anomalyResult
        ];
    }
    /**
     * 记录登录日志
     */
    private function logLogin($userId, $ip, $userAgent, $status, $riskScore = 0) {
        $stmt = $this->db->prepare("
            INSERT INTO user_login_logs 
            (user_id, login_time, ip_address, user_agent, login_status, risk_score)
            VALUES (?, NOW(), ?, ?, ?, ?)
        ");
        $stmt->execute([
            $userId,
            $ip,
            $userAgent,
            $status,
            $riskScore
        ]);
    }
    /**
     * 记录失败登录
     */
    private function logFailedLogin($username) {
        $stmt = $this->db->prepare("
            INSERT INTO user_login_logs 
            (user_id, login_time, ip_address, user_agent, login_status)
            VALUES (?, NOW(), ?, ?, 'failed')
        ");
        $stmt->execute([
            null,
            $this->getClientIp(),
            $_SERVER['HTTP_USER_AGENT'] ?? ''
        ]);
    }
    /**
     * 验证用户凭据
     */
    private function authenticateUser($username, $password) {
        $stmt = $this->db->prepare("
            SELECT * FROM users 
            WHERE (username = ? OR email = ?) 
            AND status = 'active'
            LIMIT 1
        ");
        $stmt->execute([$username, $username]);
        $user = $stmt->fetch();
        if ($user && password_verify($password, $user['password_hash'])) {
            return $user;
        }
        return false;
    }
    private function getClientIp() {
        // 同上AlertNotification中的getClientIp方法
        // ...
    }
}

用户安全面板

<?php
// SecurityDashboard.php
class SecurityDashboard {
    private $db;
    public function __construct($db) {
        $this->db = $db;
    }
    /**
     * 获取用户安全概览
     */
    public function getSecurityOverview($userId) {
        return [
            'recent_logins' => $this->getRecentLogins($userId, 10),
            'active_alerts' => $this->getActiveAlerts($userId),
            'security_settings' => $this->getSecuritySettings($userId),
            'risk_trend' => $this->getRiskTrend($userId, 30)
        ];
    }
    /**
     * 获取最近登录记录
     */
    public function getRecentLogins($userId, $limit = 10) {
        $stmt = $this->db->prepare("
            SELECT * FROM user_login_logs 
            WHERE user_id = ? 
            ORDER BY login_time DESC 
            LIMIT ?
        ");
        $stmt->execute([$userId, $limit]);
        return $stmt->fetchAll();
    }
    /**
     * 获取活跃警报
     */
    public function getActiveAlerts($userId) {
        $stmt = $this->db->prepare("
            SELECT * FROM login_alerts 
            WHERE user_id = ? 
            AND is_read = FALSE 
            ORDER BY detected_at DESC
        ");
        $stmt->execute([$userId]);
        return $stmt->fetchAll();
    }
    /**
     * 标记警报为已读
     */
    public function markAlertAsRead($alertId, $userId) {
        $stmt = $this->db->prepare("
            UPDATE login_alerts 
            SET is_read = TRUE 
            WHERE id = ? AND user_id = ?
        ");
        return $stmt->execute([$alertId, $userId]);
    }
    /**
     * 获取风险趋势数据
     */
    public function getRiskTrend($userId, $days = 30) {
        $stmt = $this->db->prepare("
            SELECT DATE(login_time) as date,
                   AVG(risk_score) as avg_risk,
                   COUNT(*) as login_count,
                   SUM(CASE WHEN login_status = 'suspicious' THEN 1 ELSE 0 END) as suspicious_count
            FROM user_login_logs 
            WHERE user_id = ? 
            AND login_time >= DATE_SUB(NOW(), INTERVAL ? DAY)
            GROUP BY DATE(login_time)
            ORDER BY date DESC
        ");
        $stmt->execute([$userId, $days]);
        return $stmt->fetchAll();
    }
}

使用示例

<?php
// example_usage.php
// 初始化数据库连接
$db = new PDO('mysql:host=localhost;dbname=your_database', 'username', 'password');
// 登录处理
$loginProcessor = new LoginProcessor($db);
$result = $loginProcessor->processLogin($_POST['username'], $_POST['password']);
if ($result['success']) {
    // 登录成功
    if (!empty($result['anomaly']['anomalies'])) {
        // 有异常但风险较低
        $_SESSION['warning'] = '检测到一些异常登录特征,请留意';
    }
    header('Location: /dashboard');
} else {
    if (isset($result['require_verification'])) {
        // 需要额外验证
        $_SESSION['require_verification'] = true;
        $_SESSION['anomaly_data'] = $result['anomaly'];
        header('Location: /verify-login');
    } else {
        // 登录失败
        $error = $result['message'];
    }
}
// 用户查看安全面板
$securityDashboard = new SecurityDashboard($db);
$securityData = $securityDashboard->getSecurityOverview($_SESSION['user_id']);
// 输出安全面板
echo json_encode($securityData);

前端提醒脚本

<!-- security_alert.js -->
<script>
class SecurityAlertManager {
    constructor(options = {}) {
        this.options = Object.assign({
            checkInterval: 60000, // 每分钟检查一次
            apiEndpoint: '/api/security/check-alerts',
            soundEnabled: true,
            notificationEnabled: true
        }, options);
        this.init();
    }
    init() {
        // 创建提醒容器
        this.createAlertContainer();
        // 开始定期检查
        this.startPeriodicCheck();
        // 监听用户活动
        this.monitorUserActivity();
    }
    createAlertContainer() {
        const container = document.createElement('div');
        container.id = 'security-alert-container';
        container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 10000;
            max-width: 400px;
        `;
        document.body.appendChild(container);
    }
    startPeriodicCheck() {
        this.checkForAlerts();
        setInterval(() => this.checkForAlerts(), this.options.checkInterval);
    }
    async checkForAlerts() {
        try {
            const response = await fetch(this.options.apiEndpoint, {
                headers: {
                    'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content
                }
            });
            const data = await response.json();
            if (data.alerts && data.alerts.length > 0) {
                this.showAlerts(data.alerts);
                this.playAlertSound();
                if (this.options.notificationEnabled) {
                    this.showBrowserNotification(data.alerts);
                }
            }
        } catch (error) {
            console.error('安全检查失败:', error);
        }
    }
    showAlerts(alerts) {
        const container = document.getElementById('security-alert-container');
        alerts.forEach(alert => {
            const alertElement = this.createAlertElement(alert);
            container.appendChild(alertElement);
            // 10秒后自动消失
            setTimeout(() => {
                alertElement.classList.add('fade-out');
                setTimeout(() => alertElement.remove(), 500);
            }, 10000);
        });
    }
    createAlertElement(alert) {
        const severityColors = {
            'high': '#dc3545',
            'medium': '#ffc107',
            'low': '#17a2b8'
        };
        const color = severityColors[alert.severity] || '#6c757d';
        const element = document.createElement('div');
        element.style.cssText = `
            background: white;
            border-left: 4px solid ${color};
            border-radius: 4px;
            padding: 15px;
            margin-bottom: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            animation: slideIn 0.3s ease-out;
        `;
        element.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: start;">
                <h4 style="margin: 0; color: ${color};">
                    ⚠️ ${this.getAlertTypeText(alert.type)}
                </h4>
                <button onclick="this.parentElement.parentElement.remove()" 
                        style="background: none; border: none; cursor: pointer;">
                    ×
                </button>
            </div>
            <p style="margin: 10px 0 0; color: #666;">
                ${alert.message}
            </p>
            <small style="color: #999;">
                ${new Date(alert.detected_at).toLocaleString()}
            </small>
            <div style="margin-top: 10px;">
                <a href="/security/check" style="color: #007bff; text-decoration: none;">
                    查看详情 →
                </a>
            </div>
        `;
        return element;
    }
    getAlertTypeText(type) {
        const types = {
            'geo_change': '地理位置异常',
            'device_change': '新设备登录',
            'frequency': '登录频率异常',
            'time_pattern': '异常登录时间'
        };
        return types[type] || '安全警报';
    }
    playAlertSound() {
        if (!this.options.soundEnabled) return;
        const audio = new Audio();
        audio.src = '/sounds/alert.mp3';
        audio.volume = 0.3;
        audio.play().catch(() => {}); // 忽略自动播放错误
    }
    showBrowserNotification(alerts) {
        if (!('Notification' in window)) return;
        if (Notification.permission === 'granted') {
            const alertCount = alerts.length;
            const body = alertCount > 1 
                ? `发现 ${alertCount} 个安全警报`
                : `安全警报: ${alerts[0].message}`;
            new Notification('安全警报', {
                body: body,
                icon: '/images/security-icon.png'
            });
        } else if (Notification.permission !== 'denied') {
            Notification.requestPermission();
        }
    }
    monitorUserActivity() {
        // 监听用户离开页面事件
        document.addEventListener('visibilitychange', () => {
            if (document.visibilityState === 'visible') {
                this.checkForAlerts();
            }
        });
    }
}
// 初始化安全提醒管理器
document.addEventListener('DOMContentLoaded', () => {
    new SecurityAlertManager({
        checkInterval: 60000,
        soundEnabled: true,
        notificationEnabled: true
    });
});
</script>

API端点

<?php
// api/security.php
header('Content-Type: application/json');
// CSRF保护
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
        http_response_code(403);
        die(json_encode(['error' => 'CSRF token mismatch']));
    }
}
$action = $_GET['action'] ?? '';
switch ($action) {
    case 'check-alerts':
        // 检查未读警报
        $securityDashboard = new SecurityDashboard($db);
        $alerts = $securityDashboard->getActiveAlerts($_SESSION['user_id']);
        echo json_encode(['alerts' => $alerts]);
        break;
    case 'mark-alert-read':
        // 标记警报为已读
        $alertId = $_POST['alert_id'];
        $securityDashboard = new SecurityDashboard($db);
        $result = $securityDashboard->markAlertAsRead($alertId, $_SESSION['user_id']);
        echo json_encode(['success' => $result]);
        break;
    case 'login-history':
        // 获取登录历史
        $securityDashboard = new SecurityDashboard($db);
        $history = $securityDashboard->getRecentLogins($_SESSION['user_id'], $_GET['limit'] ?? 10);
        echo json_encode(['history' => $history]);
        break;
    default:
        http_response_code(404);
        echo json_encode(['error' => 'Invalid action']);
}

这个异常登录检测提醒系统提供了完整的解决方案,包括:

  1. 多维度异常检测:地理位置、设备、频率、时间段
  2. 风险评分系统:自动计算登录风险等级
  3. 多通道通知:邮件、短信、推送通知
  4. 用户安全面板:查看登录历史和警报记录
  5. 前端实时提醒:浏览器通知和声音提示
  6. 安全措施建议:密码修改、双因素认证等

您可以根据实际需求调整检测阈值、通知方式和安全策略。

抱歉,评论功能暂时关闭!