使用 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