1120 字
6 分钟
thinkphp防止API盗用和实现访问统计
方案一:Referer 检查 + 访问日志
这是最简单的实现方式,适合您的场景:
PHP
<?php
namespace app\middleware;
use think\facade\Log;
class ApiProtectionMiddleware{ // 允许访问的域名列表 protected $allowedDomains = [ 'yn65.com', 'www.yn65.com', ];
public function handle($request, \Closure $next) { // 获取Referer头 $referer = $request->header('referer');
// 记录访问日志 - 无论是否允许访问都记录 $this->logAccess($request, $referer);
// 如果没有Referer,可能是直接访问或者某些浏览器/工具不发送Referer if (empty($referer)) { // 您可以决定是否允许无Referer的请求 // return response('Unauthorized', 403); return $next($request); // 允许无Referer的请求 }
// 检查Referer是否来自允许的域名 $isAllowed = false; foreach ($this->allowedDomains as $domain) { if (strpos($referer, $domain) !== false) { $isAllowed = true; break; } }
// 如果不在允许列表中 if (!$isAllowed) { return response('Unauthorized', 403); }
return $next($request); }
/** * 记录访问日志 */ protected function logAccess($request, $referer) { $logData = [ 'time' => date('Y-m-d H:i:s'), 'ip' => $request->ip(), 'url' => $request->url(true), 'referer' => $referer ?: 'none', 'user_agent' => $request->header('user-agent') ?: 'none' ];
// 使用ThinkPHP的日志系统记录 Log::write(json_encode($logData), 'api_access');
// 可选:将访问记录保存到数据库 // \app\model\ApiAccessLog::create($logData); }}方案二:API密钥 + 域名绑定
更安全的方法是使用API密钥:
PHP
<?php
namespace app\middleware;
use think\facade\Log;
class ApiKeyMiddleware{ public function handle($request, \Closure $next) { // 获取API密钥 - 可以通过查询参数或请求头 $apiKey = $request->param('api_key') ?: $request->header('X-API-Key');
// 获取Referer $referer = $request->header('referer');
// 记录访问 $this->logAccess($request, $referer, $apiKey);
// 如果没有API密钥 if (empty($apiKey)) { return response('API key required', 403); }
// 验证API密钥并检查它是否与允许的域名绑定 $keyInfo = $this->validateApiKey($apiKey); if (!$keyInfo) { return response('Invalid API key', 403); }
// 如果有Referer,检查它是否与API密钥绑定的域名匹配 if (!empty($referer) && !empty($keyInfo['domain'])) { if (strpos($referer, $keyInfo['domain']) === false) { return response('Unauthorized domain for this API key', 403); } }
return $next($request); }
/** * 验证API密钥 * 实际应用中,这应该从数据库查询 */ protected function validateApiKey($apiKey) { // 示例API密钥映射表 - 实际应用中应该从数据库获取 $apiKeys = [ 'key_for_yn65' => [ 'domain' => 'yn65.com', 'rate_limit' => 1000, // 每日请求限制 'owner' => 'YN65网站' ], 'key_for_8yun' => [ 'domain' => 'yn65.com', 'rate_limit' => 1000, 'owner' => 'yn65网站' ] ];
return isset($apiKeys[$apiKey]) ? $apiKeys[$apiKey] : false; }
/** * 记录访问日志 */ protected function logAccess($request, $referer, $apiKey) { $logData = [ 'time' => date('Y-m-d H:i:s'), 'ip' => $request->ip(), 'url' => $request->url(true), 'referer' => $referer ?: 'none', 'api_key' => $apiKey ?: 'none', 'user_agent' => $request->header('user-agent') ?: 'none' ];
// 使用ThinkPHP的日志系统记录 Log::write(json_encode($logData), 'api_access');
// 可选:将访问记录保存到数据库 // \app\model\ApiAccessLog::create($logData); }}方案三:结合统计面板的完整解决方案
- 创建一个数据库表来存储API访问日志
- 创建一个统计面板来查看访问数据
- 实现API密钥管理功能
数据库表结构示例
SQL
CREATE TABLE `api_access_log` ( `id` int(11) NOT NULL AUTO_INCREMENT, `api_key` varchar(64) DEFAULT NULL, `ip` varchar(45) NOT NULL, `url` varchar(255) NOT NULL, `referer` varchar(255) DEFAULT NULL, `user_agent` varchar(255) DEFAULT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`id`), KEY `idx_api_key` (`api_key`), KEY `idx_created_at` (`created_at`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `api_keys` ( `id` int(11) NOT NULL AUTO_INCREMENT, `api_key` varchar(64) NOT NULL, `domain` varchar(255) NOT NULL, `owner` varchar(255) NOT NULL, `rate_limit` int(11) NOT NULL DEFAULT 1000, `is_active` tinyint(1) NOT NULL DEFAULT 1, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_api_key` (`api_key`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;中间件实现
PHP
<?php
namespace app\middleware;
use app\model\ApiAccessLog;use app\model\ApiKey;
class ApiAccessMiddleware{ public function handle($request, \Closure $next) { // 获取API密钥 $apiKey = $request->param('api_key') ?: $request->header('X-API-Key');
// 获取Referer $referer = $request->header('referer');
// 记录访问 $this->logAccess($request, $referer, $apiKey);
// 如果您想要强制要求API密钥 if (empty($apiKey)) { // 可以选择是否允许无密钥访问 // return response('API key required', 403); } else { // 验证API密钥 $keyInfo = ApiKey::where('api_key', $apiKey) ->where('is_active', 1) ->find();
if (!$keyInfo) { return response('Invalid API key', 403); }
// 检查域名绑定 if (!empty($referer) && !empty($keyInfo['domain'])) { if (strpos($referer, $keyInfo['domain']) === false) { return response('Unauthorized domain for this API key', 403); } }
// 检查速率限制 // ...这里可以添加速率限制逻辑 }
return $next($request); }
/** * 记录访问日志 */ protected function logAccess($request, $referer, $apiKey) { ApiAccessLog::create([ 'api_key' => $apiKey ?: null, 'ip' => $request->ip(), 'url' => $request->url(true), 'referer' => $referer ?: null, 'user_agent' => $request->header('user-agent') ?: null, 'created_at' => date('Y-m-d H:i:s') ]); }}前端使用示例
对于您的静态网站,您可以这样使用API:
<!-- 使用Referer检查方式 --><div style="background: url(http://api.xxx.com/image)"></div>
<!-- 使用API密钥方式 --><div style="background: url(http://api.xxx.com/image?api_key=your_api_key)"></div>建议的实施方案
- 先实施方案一:这是最简单的,可以快速部署,并开始收集使用数据
- 分析访问数据:查看谁在使用您的API,以及使用模式
- 根据需要升级:如果发现未授权使用严重,可以升级到方案二或方案三
这种渐进式的方法让您可以根据实际情况调整保护级别,同时不会过度复杂化初始实现。