帳號安全防護中,偵測「非常用裝置/地點登入」是基本需求。本文示範如何結合客戶端 IP 取得、資料庫歷史登入記錄比對、閾值判斷,實作一個輕量級異常 IP 標記功能,適用於各類 PHP 框架。
/**
* 判斷登入 IP 是否異常
*
* @param string $bindIp 使用者綁定/上次登入 IP
* @param string $username 使用者帳號
* @return string HTML 字串,含顏色標記
*/
function checkLoginIp(string $bindIp, string $username): string {
$currentIp = getClientIp();
if ($bindIp === $currentIp) {
return '登入 IP:' . $currentIp . '';
}
// 查詢歷史登入次數(假設使用 ThinkPHP 模型,可替換為 PDO/DBAL)
$count = \think\Db::name('login_log')
->where('username', $username)
->where('ip', $currentIp)
->count();
$threshold = 10; // 信任閾值,可從配置讀取
$color = $count > $threshold ? 'blue' : 'red';
$label = $count > $threshold ? '登入 IP' : '異常 IP';
return '' . $label . ':' . $currentIp . '';
}
/**
* 取得真實客戶端 IP(支援代理、CDN、負載均衡)
*/
function getClientIp(): string {
$headers = [
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR', // 可能包含多個 IP,取第一個非 unknown
'HTTP_X_REAL_IP', // Nginx proxy_set_header
'REMOTE_ADDR'
];
foreach ($headers as $header) {
$value = $_SERVER[$header] ?? '';
if ($value) {
// X-Forwarded-For 可能是 "client, proxy1, proxy2"
$ips = array_map('trim', explode(',', $value));
foreach ($ips as $ip) {
if ($ip !== 'unknown' && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
CREATE TABLE `login_log` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(64) NOT NULL COMMENT '登入帳號',
`ip` VARBINARY(16) NOT NULL COMMENT 'IPv4/IPv6 二進位儲存,INET6_ATON()',
`user_agent` VARCHAR(255) DEFAULT NULL,
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1成功 0失敗',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_user_ip` (`username`, `ip`),
INDEX `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
IP 建議用 VARBINARY(16) 儲存(INET6_ATON()/INET6_NTOA()),節省空間且利於範圍查詢。
X-Forwarded-For 可偽造,必須配合伺服器層(Nginx/Apache)設定 real_ip 模組,只信任可信代理層傳遞的 Header。異常 IP 偵測是帳號安全的第一道防線。從正確取得真實 IP 開始,結合歷史行為建模,再逐步引入裝置指紋與風險評分,構建分層防護體系。