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);
}
}

方案三:结合统计面板的完整解决方案#

  1. 创建一个数据库表来存储API访问日志
  2. 创建一个统计面板来查看访问数据
  3. 实现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>

建议的实施方案#

  1. 先实施方案一:这是最简单的,可以快速部署,并开始收集使用数据
  2. 分析访问数据:查看谁在使用您的API,以及使用模式
  3. 根据需要升级:如果发现未授权使用严重,可以升级到方案二或方案三

这种渐进式的方法让您可以根据实际情况调整保护级别,同时不会过度复杂化初始实现。

thinkphp防止API盗用和实现访问统计
https://www.yn65.com/posts/a14/
作者
晨平安
发布于
2025-10-22
许可协议
CC BY-NC-SA 4.0
封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00