如何在 PHP (Laravel) 中验证请求的源域
Posted
技术标签:
【中文标题】如何在 PHP (Laravel) 中验证请求的源域【英文标题】:How to verify the origin domain of a request in PHP (Laravel) 【发布时间】:2020-10-18 19:11:32 【问题描述】:我正在开发一个 Laravel 应用程序,我正在为其他网站构建一些 API。但我试图让我的 API 的实现尽可能简单。我的期望是用户只会在 html 头中使用这个标签:
<script src="api.mydomain.com">
现在我在这个 URL 上有一个控制器,它为源 javascript 提供内容类型标头,但在它到达那里之前,路由器将首先执行我的身份验证中间件。假设它看起来像这样:
public static $users = [
'client1.com',
'client2.com',
'client3.com'
];
public function handle(Request $request, Closure $next)
$origin = "HERE I NEED THE ORIGIN URL"; // e.g. client4.com
if ( !in_array($origin, self::$users) )
abort(401);
return $next($request);
从代码中可以看出,我需要检索 $origin 变量。因此,如果网站 client1.com 将尝试插入我的 javascript,它将成功获取 javascript 代码。如果 client4.com 尝试访问它,它将收到 401 错误。
我发现了使用 $_SERVER['HTTP_REFERER'] 或 Laravel 的 $request->server('HTTP_REFERER') 的方法,但是这些数据可能是伪造的,对吧?
在最好的情况下,我想检索原始域,当不可用时(例如,来自私有 cURL 请求),我想获取 IP 地址。当然,我需要它是安全的——clients1/2/3 为我的 API 付费,其他人没有。
我该怎么做?或者有没有更好的原产地认证方法?
【问题讨论】:
看看this @jewishmoses 是的,我想到了 CORS,但据我所知,您只能在此标头中定义一个域。我什至看到了一些网站,它们作为代理工作,可以克服 CORS 保护(我不知道他们是如何做到的)。 【参考方案1】:所有引用的东西都可以被欺骗。
付费 API 的最佳方式是颁发 API 调用密钥。
您的 API 可以显示结果或错误,具体取决于客户端是否具有正确的 API 密钥并已付费。
您还应该为 API 调用保留带有时间戳、客户端 ID 和 IP 地址的日志表。因此,您可以不时从呼叫频率和 IP 模式检查您的付费客户是否与其他人共享他的密钥。
不时清理此日志表以保持其小而高效。
【讨论】:
但是客户端将如何使用密钥呢?如果他们会使用<script src="api.mydomain.com" key="secretKey">
之类的东西,那么每个要检查他们的代码的人都可以找到密钥并在他们的网站上插入脚本。正如您所提到的 - 我必须检查是否会看到一些可疑活动,但一些大型网站使用多个服务器 (IP)。
好的,那么对于这种情况,还可以为您的付费客户提供一些密钥,该密钥可以最终解密 API 数据。这样,只有他们才能理解。我知道您想简化它,但它变得太简单而无法保证安全,因此您必须以这种或那种方式添加一些东西以使其安全。
如果我想象我的客户会得到一些解密密钥,我会假设只要 JS 是一种前端语言,他们的访问者仍然能够阅读该脚本,因为它实际上会被解密在浏览器中。
如果客户端在服务器端语言上进行 API 调用,然后将结果输出为 JS,则不会。人们只会看到 JS 而不是底层代码。
这需要服务器端实现,这对于使用一些预构建解决方案的客户端来说可能很困难。但是,人们仍然可以复制解密后的 JS 代码,不是吗?【参考方案2】:
所以我通过在中间件处理程序中添加标题(感谢@jewishmoses 的灵感)来解决这个问题。我的 Javascript 基本上对所有人都可用,但它只提供了一个按钮,它试图在内部创建一个带有 iframe 的新元素(我的应用程序也可用作 API)。
假设我在服务器上有一个关联数组,我可以从任何数据库动态填充:
$clients = [
'client1' => 'paying-customer.com',
'client2' => 'also-paying-customer.com',
];
...我的 API 路由定义为 'api.mydomain.com/client' 和 'api.mydomain.com/client/iframe' 用于 iframed 应用程序。这个处理程序负责添加标题:
public function handle(Request $request, Closure $next)
$client = $request->route('client',null);
$clientSet = $client !== null;
$clientAccepted = isset($clients[$client]);
if ( $clientSet and !$clientAccepted )
abort(401);
$response = $next($request);
if( $clientSet and isset($response->headers) and $response->headers instanceof ResponseHeaderBag)
$clientDomain = $clients[$client];
$response->headers->add([
'Content-Security-Policy' => "frame-ancestors https://*.$clientDomain/ https://$clientDomain/"
]);
return $response;
现在会发生什么:
-
client1 成功从 api.mydomain.com/client1 导入 javascript,它将尝试访问 api.mydomain.com/client1/iframe(也成功)李>
client3 尝试从 api.mydomain.com/client3 导入 javascript 失败
client3 成功从 api.mydomain.com/client1 导入 javascript,它将尝试访问 api.mydomain.com/client1/iframe(被 headers 拒绝)
也许有一种更优雅的方式来阻止加载 javascript,但在我看来,将我自己的应用程序作为 API(在 iframe 中)提供是足够安全的,因为我可以控制谁可以使用它,而现代浏览器将帮助我停止小偷们”。这个resource 对解决我的问题很有帮助。
【讨论】:
以上是关于如何在 PHP (Laravel) 中验证请求的源域的主要内容,如果未能解决你的问题,请参考以下文章
如何在没有密钥或没有验证的情况下解码 php/laravel 中的 jwt?
在rest api app中生成laravel请求验证的最佳实践