使用 AJAX 和 PHP 安全 API 调用到第 3 方 API
Secure API calls with AJAX and PHP to 3rd party API
我想对第 3 方 API 进行 GET、POST 和 PUT 调用,并通过 AJAX 在客户端显示响应。 API 调用需要一个令牌,但我需要将该令牌保密/不在客户端 JS 代码中。
我见过一些 suggestions like this one 在中间有服务器端代码,这些代码会被 AJAX 查询,并会处理实际的 API 调用。我可以直接使用 AJAX 中的 API,但我不确定如何使用两步流程来对用户隐藏令牌。我的谷歌搜索没有找到任何关于实现此目标的最佳实践方法的指示。
在我的例子中,中间的服务器是 运行 PHP,因此我假设 cURL / Guzzle 是使用令牌进行 API 调用的直接选项。 API 响应将是 JSON。
谁能给我一个粗略的例子,说明如何使用 jQuery.ajax()、PHP、第三者 API?
或者,如果有任何优质资源详细介绍了此方法,我将不胜感激 link。同样,如果这是一种糟糕的使用方法,那么很高兴知道为什么。
编辑
可能值得注意的是,我希望尽可能灵活地部署它;它将在具有独特配置的多个站点上使用,因此理想情况下,这将在不改变服务器或托管帐户配置的情况下实现。
因为你想要的只是向 http headers
添加令牌,我假设是 Authorization
一个简单的方法是实现一个代理服务器来调用你的 api添加这些后的端点。 nginx
的样本文件是
location /apiProxy {
proxy_pass http://www.apiendPoint.com/;
proxy_set_header Authorization <secret token>;
}
与编写程序相比,这是一种更智能的方法,只需 4 行代码即可完成。确保相应地更改您的参数,并根据您正在使用的 api 客户端的需要添加其他参数。 javascript 方面的唯一区别是使用 location url
而不是充当代理的服务提供的服务。
编辑
apache
的配置为
NameVirtualHost *
<VirtualHost *>
<LocationMatch "/apiProxy">
ProxyPass http://www.apiendPoint.com/
ProxyPassReverse http://www.apiendPoint.com/
Header add Authorization "<secret token>"
RequestHeader set Authorization "<secret token>"
</LocationMatch>
</VirtualHost>
如果您使用 cUrl,您必须保护的是您的服务器。我个人使用的方式是 Google reCaptcha,它肯定是用来解决像你这样的问题的。这里很好地解释了客户端和服务器端的集成。
https://webdesign.tutsplus.com/tutorials/how-to-integrate-no-captcha-recaptcha-in-your-website--cms-23024
通过这种方式,您无需更改虚拟主机文件和任何 Apache 配置中的任何内容。
没有示例代码有点难。但据我了解,您可以按照此操作,
AJAX呼叫
$.ajax({
type: "POST",
data: {YOU DATA},
url: "yourUrl/anyFile.php",
success: function(data){
// do what you need to
}
});
在PHP
收集您发布的数据并处理 API,类似这样的事情
$data = $_POST['data'];
// lets say your data something like this
$data =array("line1" => "line1", "line2"=>"line1", "line3" =>"line1");
$api = new Api();
$api->PostMyData($data );
例子APIClass
class Api
{
const apiUrl = "https://YourURL/ ";
const targetEndPoint = self::apiUrl. "someOtherPartOFurl/";
const key = "someKey819f053bb08b795343e0b2ebc75fb66f";
const secret ="someSecretef8725578667351c9048162810c65d17";
private $autho="";
public function PostMyData($data){
$createOrder = $this->callApi("POST", self::targetEndPoint, $data, true);
return $createOrder;
}
private function callApi($method, $url, $data=null, $authoRequire = false){
$curl = curl_init();
switch ($method)
{
case "POST":
curl_setopt($curl, CURLOPT_POST, 1);
if ($data)
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));
break;
case "PUT":
curl_setopt($curl, CURLOPT_PUT, 1);
break;
default:
if ($data)
$url = sprintf("%s?%s", $url, http_build_query($data));
}
if($authoRequire){
$this->autho = self::key.":".self::secret;
// Optional Authentication:
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($curl, CURLOPT_USERPWD, $this->autho);
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
curl_close($curl);
return $result;
}
}
根据您的要求,"server-side code in the middle" 中继(代理)脚本似乎是最佳选择。
。 N.B。为了处理 CURL 错误,它 returns 一个新的 "object" 包含 ['status']('OK' 或有关 CURL 失败的信息)和 ['msg'] 包含来自的实际响应API 提供商。在您的 JS 中,原来的 API "object" 现在需要在 'msg'.
下提取一级
基本Relays/Proxies可以绕过
如果您使用中继脚本,那么寻找 API 密钥的人会 可能 尝试其他地方。然而;盗版者可以简单地用您的 API 密钥替换他对 API 提供商的调用,并调用您的脚本(您的 API 密钥仍将被使用)。
运行 搜索引擎机器人 AJAX/relay 脚本
Google 机器人(其他?)执行 AJAX。我 假设 (中继与否)如果你的 AJAX 不需要用户输入,那么机器人访问将导致 API 密钥使用。机器人 "improving"。将来(现在?)他们可能会模拟用户输入,例如如果从下拉列表中选择一个城市导致 API 请求,那么 Google 可能会循环下拉选项。
如果担心,您可以在中继脚本中包含一个检查,例如
$bots = array('bot','slurp','crawl','spider','curl','facebook','fetch','mediapartners','scan','google'); // add your own
foreach ($bots as $bot) :
if (strpos( strtolower($_SERVER['HTTP_USER_AGENT']), $bot) !== FALSE): // its a BOT
// exit error msg or default content for search indexing (in a format expected by your JS)
exit (json_encode(array('status'=>"bot")));
endif;
endforeach;
用于解决上述问题的中继脚本和附加代码
不要过度保护盗版;中继应该很快,延迟不会被访客注意到。可能的解决方案(没有专家并且对会话生疏):
1: PHP 会话解决方案
检查中继是否由在过去 15 分钟内访问过您的 AJAX 页面、提供了有效令牌并具有相同用户代理和 IP 地址的人调用。
您的 Ajax 页面 将以下代码段添加到您的 PHP 和 JS:
ini_set('session.cookie_httponly', 1 );
session_start();
// if expired or a "new" visitor
if (empty($_SESSION['expire']) || $_SESSION['expire'] < time()) $_SESSION['token'] = md5('xyz' . uniqid(microtime())); // create token (fast/sufficient)
$_SESSION['expire'] = time() + 900; // make session valid for next 15 mins
$_SESSION['visitid'] = $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'];
...
// remove API key from your AJAX and add token value to JS e.g.
$.ajax({type:"POST", url:"/path/relay.php",data: yourQueryParams + "&token=<?php echo $_SESSION['token']; ?>", success: function(data){doResult(data);} });
relay/proxy 脚本(会话版本):
使用 并在 CURL 块之前添加:
session_start(); // CHECK REQUEST IS FROM YOU AJAX PAGE
if (empty($_SESSION['token']) || $_SESSION['token'] != $_POST['token'] || $_SESSION['expire'] < time()
|| $_SESSION['visitid'] != $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] ) {
session_destroy(); // (invalid) clear session's variables, you could also kill session/cookie
exit (json_encode(array('status'=>'blocked'))); // exit an object that can be understood by your JS
}
采用标准会话 ini 设置。在同一域中需要 Cookie 和 page/relay(可行的解决方法)。会话可能会影响性能。如果网站已经使用会话,代码将需要考虑到这一点。
2: Sessionless/Cookieless 选项
使用与特定 IP 地址和用户代理关联的令牌,最长有效期为 2 小时。
页面和中继都使用的函数 例如"site-functions.inc":
<?php
function getToken($thisHour = TRUE) { // provides token to insert on page or to compare with the one from page
if ($thisHour) $theHour = date("jH"); else $theHour = date("jH", time() -3600); // token for current or previous hour
return hash('sha256', 'salt' . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] . $theHour);
}
function isValidToken($token) { // is token valid for current or previous hour
return (getToken() == $token || getToken(FALSE) == $token);
}
?>
中继脚本 使用 并在 CURL 块之前添加:
// assign post variable 'token' to $token
include '/pathTo/' . 'site-functions.inc';
$result = array('status'=>'timed out (try reloading) or invalid request');
if ( ! isValidToken($token)) exit(json_encode(array('msg'=>'invalid/timeout'))); // in format for handling by your JS
需要 API(或您的 javascript 包含文件)的页面:
<?php include '/pathTo/' . 'site-functions.inc'; ?>
...
// example Javascript with PHP insertion of token value
var dataString = existingDataString + "&token=" + "<?php echo getToken(); ?>"
jQuery.ajax({type:"POST", url:"/whatever/myrelay.php",data: dataString, success: function(data){myOutput(data);} });
注意:用户代理是可欺骗的。 IP (REMOTE_ADDR) "cannot" 是伪造的,但在少数网站上设置可能会导致问题,例如如果您使用 NGINX,您可能会发现 REMOTE_ADDR 始终包含 NGINX 服务器 IP。
如果您使用的是典型的第 3 方 API,它将提供非敏感信息,直到您达到 API 密钥的使用上限,那么(我认为)上述解决方案应该足够了。
正如人们指出的那样,您希望服务器上的代理方法隐藏 API-key。
为避免在服务器上滥用您的方法,请使用一次性令牌(就像您通常用于表单的方式)保护调用 - 从您的服务器生成(不在 javascript..)。
我不喜欢上面粘贴的代码,它检查已知的 http-user 代理...或站点令牌...这不安全。
我会使用@MMRahman 发布的解决方案,如果你想在你的后端和前端之间添加一个安全层,你可以做的是当用户登录时生成一个唯一的 ID,将它存储在服务器会话中在浏览器的 cookie 或 local/session 存储中,这样当您使用 ajax 调用后端时,您可以从存储在浏览器中的位置获取值,并检查这些值是否相同,如果是,则调用外部 api 和 return 值,如果不忽略请求。
所以总结一下:
用户登录 -> 生成唯一 ID -> 将其存储在服务器会话和浏览器会话中 -> 从 ajax 进行调用,将浏览器会话中的值作为参数传递 -> 检查它是否与服务器会话存储的值匹配 -> 如果是使用存储在后端/数据库/文件/任何你想要的令牌调用外部 api
我想对第 3 方 API 进行 GET、POST 和 PUT 调用,并通过 AJAX 在客户端显示响应。 API 调用需要一个令牌,但我需要将该令牌保密/不在客户端 JS 代码中。
我见过一些 suggestions like this one 在中间有服务器端代码,这些代码会被 AJAX 查询,并会处理实际的 API 调用。我可以直接使用 AJAX 中的 API,但我不确定如何使用两步流程来对用户隐藏令牌。我的谷歌搜索没有找到任何关于实现此目标的最佳实践方法的指示。
在我的例子中,中间的服务器是 运行 PHP,因此我假设 cURL / Guzzle 是使用令牌进行 API 调用的直接选项。 API 响应将是 JSON。
谁能给我一个粗略的例子,说明如何使用 jQuery.ajax()、PHP、第三者 API?
或者,如果有任何优质资源详细介绍了此方法,我将不胜感激 link。同样,如果这是一种糟糕的使用方法,那么很高兴知道为什么。
编辑
可能值得注意的是,我希望尽可能灵活地部署它;它将在具有独特配置的多个站点上使用,因此理想情况下,这将在不改变服务器或托管帐户配置的情况下实现。
因为你想要的只是向 http headers
添加令牌,我假设是 Authorization
一个简单的方法是实现一个代理服务器来调用你的 api添加这些后的端点。 nginx
的样本文件是
location /apiProxy {
proxy_pass http://www.apiendPoint.com/;
proxy_set_header Authorization <secret token>;
}
与编写程序相比,这是一种更智能的方法,只需 4 行代码即可完成。确保相应地更改您的参数,并根据您正在使用的 api 客户端的需要添加其他参数。 javascript 方面的唯一区别是使用 location url
而不是充当代理的服务提供的服务。
编辑
apache
的配置为
NameVirtualHost *
<VirtualHost *>
<LocationMatch "/apiProxy">
ProxyPass http://www.apiendPoint.com/
ProxyPassReverse http://www.apiendPoint.com/
Header add Authorization "<secret token>"
RequestHeader set Authorization "<secret token>"
</LocationMatch>
</VirtualHost>
如果您使用 cUrl,您必须保护的是您的服务器。我个人使用的方式是 Google reCaptcha,它肯定是用来解决像你这样的问题的。这里很好地解释了客户端和服务器端的集成。 https://webdesign.tutsplus.com/tutorials/how-to-integrate-no-captcha-recaptcha-in-your-website--cms-23024 通过这种方式,您无需更改虚拟主机文件和任何 Apache 配置中的任何内容。
没有示例代码有点难。但据我了解,您可以按照此操作,
AJAX呼叫
$.ajax({
type: "POST",
data: {YOU DATA},
url: "yourUrl/anyFile.php",
success: function(data){
// do what you need to
}
});
在PHP
收集您发布的数据并处理 API,类似这样的事情
$data = $_POST['data'];
// lets say your data something like this
$data =array("line1" => "line1", "line2"=>"line1", "line3" =>"line1");
$api = new Api();
$api->PostMyData($data );
例子APIClass
class Api
{
const apiUrl = "https://YourURL/ ";
const targetEndPoint = self::apiUrl. "someOtherPartOFurl/";
const key = "someKey819f053bb08b795343e0b2ebc75fb66f";
const secret ="someSecretef8725578667351c9048162810c65d17";
private $autho="";
public function PostMyData($data){
$createOrder = $this->callApi("POST", self::targetEndPoint, $data, true);
return $createOrder;
}
private function callApi($method, $url, $data=null, $authoRequire = false){
$curl = curl_init();
switch ($method)
{
case "POST":
curl_setopt($curl, CURLOPT_POST, 1);
if ($data)
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));
break;
case "PUT":
curl_setopt($curl, CURLOPT_PUT, 1);
break;
default:
if ($data)
$url = sprintf("%s?%s", $url, http_build_query($data));
}
if($authoRequire){
$this->autho = self::key.":".self::secret;
// Optional Authentication:
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($curl, CURLOPT_USERPWD, $this->autho);
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
curl_close($curl);
return $result;
}
}
根据您的要求,"server-side code in the middle" 中继(代理)脚本似乎是最佳选择。
基本Relays/Proxies可以绕过
如果您使用中继脚本,那么寻找 API 密钥的人会 可能 尝试其他地方。然而;盗版者可以简单地用您的 API 密钥替换他对 API 提供商的调用,并调用您的脚本(您的 API 密钥仍将被使用)。
运行 搜索引擎机器人 AJAX/relay 脚本
Google 机器人(其他?)执行 AJAX。我 假设 (中继与否)如果你的 AJAX 不需要用户输入,那么机器人访问将导致 API 密钥使用。机器人 "improving"。将来(现在?)他们可能会模拟用户输入,例如如果从下拉列表中选择一个城市导致 API 请求,那么 Google 可能会循环下拉选项。
如果担心,您可以在中继脚本中包含一个检查,例如
$bots = array('bot','slurp','crawl','spider','curl','facebook','fetch','mediapartners','scan','google'); // add your own
foreach ($bots as $bot) :
if (strpos( strtolower($_SERVER['HTTP_USER_AGENT']), $bot) !== FALSE): // its a BOT
// exit error msg or default content for search indexing (in a format expected by your JS)
exit (json_encode(array('status'=>"bot")));
endif;
endforeach;
用于解决上述问题的中继脚本和附加代码
不要过度保护盗版;中继应该很快,延迟不会被访客注意到。可能的解决方案(没有专家并且对会话生疏):
1: PHP 会话解决方案
检查中继是否由在过去 15 分钟内访问过您的 AJAX 页面、提供了有效令牌并具有相同用户代理和 IP 地址的人调用。
您的 Ajax 页面 将以下代码段添加到您的 PHP 和 JS:
ini_set('session.cookie_httponly', 1 );
session_start();
// if expired or a "new" visitor
if (empty($_SESSION['expire']) || $_SESSION['expire'] < time()) $_SESSION['token'] = md5('xyz' . uniqid(microtime())); // create token (fast/sufficient)
$_SESSION['expire'] = time() + 900; // make session valid for next 15 mins
$_SESSION['visitid'] = $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'];
...
// remove API key from your AJAX and add token value to JS e.g.
$.ajax({type:"POST", url:"/path/relay.php",data: yourQueryParams + "&token=<?php echo $_SESSION['token']; ?>", success: function(data){doResult(data);} });
relay/proxy 脚本(会话版本):
使用
session_start(); // CHECK REQUEST IS FROM YOU AJAX PAGE
if (empty($_SESSION['token']) || $_SESSION['token'] != $_POST['token'] || $_SESSION['expire'] < time()
|| $_SESSION['visitid'] != $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] ) {
session_destroy(); // (invalid) clear session's variables, you could also kill session/cookie
exit (json_encode(array('status'=>'blocked'))); // exit an object that can be understood by your JS
}
采用标准会话 ini 设置。在同一域中需要 Cookie 和 page/relay(可行的解决方法)。会话可能会影响性能。如果网站已经使用会话,代码将需要考虑到这一点。
2: Sessionless/Cookieless 选项
使用与特定 IP 地址和用户代理关联的令牌,最长有效期为 2 小时。
页面和中继都使用的函数 例如"site-functions.inc":
<?php
function getToken($thisHour = TRUE) { // provides token to insert on page or to compare with the one from page
if ($thisHour) $theHour = date("jH"); else $theHour = date("jH", time() -3600); // token for current or previous hour
return hash('sha256', 'salt' . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] . $theHour);
}
function isValidToken($token) { // is token valid for current or previous hour
return (getToken() == $token || getToken(FALSE) == $token);
}
?>
中继脚本 使用
// assign post variable 'token' to $token
include '/pathTo/' . 'site-functions.inc';
$result = array('status'=>'timed out (try reloading) or invalid request');
if ( ! isValidToken($token)) exit(json_encode(array('msg'=>'invalid/timeout'))); // in format for handling by your JS
需要 API(或您的 javascript 包含文件)的页面:
<?php include '/pathTo/' . 'site-functions.inc'; ?>
...
// example Javascript with PHP insertion of token value
var dataString = existingDataString + "&token=" + "<?php echo getToken(); ?>"
jQuery.ajax({type:"POST", url:"/whatever/myrelay.php",data: dataString, success: function(data){myOutput(data);} });
注意:用户代理是可欺骗的。 IP (REMOTE_ADDR) "cannot" 是伪造的,但在少数网站上设置可能会导致问题,例如如果您使用 NGINX,您可能会发现 REMOTE_ADDR 始终包含 NGINX 服务器 IP。
如果您使用的是典型的第 3 方 API,它将提供非敏感信息,直到您达到 API 密钥的使用上限,那么(我认为)上述解决方案应该足够了。
正如人们指出的那样,您希望服务器上的代理方法隐藏 API-key。
为避免在服务器上滥用您的方法,请使用一次性令牌(就像您通常用于表单的方式)保护调用 - 从您的服务器生成(不在 javascript..)。
我不喜欢上面粘贴的代码,它检查已知的 http-user 代理...或站点令牌...这不安全。
我会使用@MMRahman 发布的解决方案,如果你想在你的后端和前端之间添加一个安全层,你可以做的是当用户登录时生成一个唯一的 ID,将它存储在服务器会话中在浏览器的 cookie 或 local/session 存储中,这样当您使用 ajax 调用后端时,您可以从存储在浏览器中的位置获取值,并检查这些值是否相同,如果是,则调用外部 api 和 return 值,如果不忽略请求。
所以总结一下: 用户登录 -> 生成唯一 ID -> 将其存储在服务器会话和浏览器会话中 -> 从 ajax 进行调用,将浏览器会话中的值作为参数传递 -> 检查它是否与服务器会话存储的值匹配 -> 如果是使用存储在后端/数据库/文件/任何你想要的令牌调用外部 api