使用AJAX和PHP对第三方API进行安全API调用

t-j*_*jam 10 javascript php ajax jquery php-curl

我想对第三方API进行GET,POST和PUT调用,并通过AJAX在客户端显示响应.API调用需要一个令牌,但我需要将该令牌保密/不在客户端JS代码中.

我已经看到了一些像这样的建议,中间的服务器端代码将由AJAX查询,并将处理实际的API调用.我可以直接使用AJAX的API,但我不确定如何使用两步过程来隐藏用户的令牌.我的谷歌搜索没有找到任何关于实现这一目标的最佳实践方法的指示.

在我的情况下,中间的服务器将运行PHP,因此我假设cURL/Guzzle是使用令牌进行API调用的直接选项.API响应将是JSON.

任何人都可以给我一个粗略的例子,说明如何使用jQuery.ajax(),PHP,第三方API实现这一目标?

或者,如果有任何质量资源详细介绍了这种方法,我会很感激.同样,如果这是一种可怕的使用方法,那么知道原因会很棒.

编辑
可能值得注意的是,我希望尽可能灵活地部署它; 它将用于具有唯一配置的多个站点,因此理想情况下,这将在不改变服务器或主机帐户配置的情况下实现.

geo*_*oot 8

因为你想要的只是添加令牌http headers,我假设Authorization一个简单的方法是实现一个代理服务器,在添加后调用你的api端点.nginx将是一个示例文件

location /apiProxy {
    proxy_pass http://www.apiendPoint.com/;
    proxy_set_header Authorization <secret token>;
}
Run Code Online (Sandbox Code Playgroud)

这是一种更聪明的方法,而不是编写程序,并使用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>
Run Code Online (Sandbox Code Playgroud)


MMR*_*man 7

没有示例代码就有点困难.但据我所知你可以遵循这个,

AJAX CALL

$.ajax({
        type: "POST",
        data: {YOU DATA},
        url: "yourUrl/anyFile.php",
        success: function(data){
           // do what you need to 

            }
        });
Run Code Online (Sandbox Code Playgroud)

在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 );
Run Code Online (Sandbox Code Playgroud)

示例API类

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;

 }
}
Run Code Online (Sandbox Code Playgroud)

  • 这种方法可以工作,进行额外的IP和主机名检查,以确保它是服务器自己.我有一个类似但更复杂的解决方案,在多个api上使用这种方法需要这样的东西.我还建议不要在代码库中放入敏感的密钥和令牌信息,使用DotEnv包并使用gitigonore中忽略的.env文件并在其中放置不同的环境密钥. (2认同)

scy*_*ale 5

从您的要求来看,“中间的服务器端代码”中继(代理)脚本似乎是最好的选择。

PHP 示例在这里。注意,为了处理 CURL 错误,它返回一个新的“对象”,其中包含 ['status']('OK' 或 CURL 失败信息)和 ['msg'],其中包含来自 API 提供者的实际响应。在 JS 中,原始 API“对象”现在需要在“msg”下提取下一级。

基本中继/代理可以被绕过

如果您使用中继脚本,那么寻找 API 密钥的人可能会尝试其他地方。然而; 盗版者可以简单地使用您的 API 密钥替换对 API 提供商的调用,而调用您的脚本(并且您的 API 密钥仍将被使用)。

通过搜索引擎机器人运行 AJAX/中继脚本

Google 机器人(其他?)执行 AJAX。我假设(无论是否中继)如果您的 AJAX 不需要用户输入,那么机器人访问将导致 API 密钥的使用。机器人正在“改进”。将来(现在?)他们可能会模拟用户输入,例如,如果从下拉列表中选择一个城市会导致 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;
Run Code Online (Sandbox Code Playgroud)

中继脚本和附加代码来解决上述问题

不要过度保护海盗;中继应该快速且延迟不会被游客察觉。可能的解决方案(没有专家并且会话生锈):

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);} });
Run Code Online (Sandbox Code Playgroud)

  中继/代理脚本(会话版本):

  使用现有的示例中继脚本并在 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
  }
Run Code Online (Sandbox Code Playgroud)

  假定标准会话 ini 设置。需要 Cookie 并在同一域上进行寻呼/中继(可能有解决方法)。会话可能会影响性能。如果站点已经使用会话,代码将需要考虑到这一点。

2:无会话/无 Cookie 选项

  使用与特定 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);
}
?>
Run Code Online (Sandbox Code Playgroud)

  中继脚本使用现有示例并在 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
Run Code Online (Sandbox Code Playgroud)

  需要 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);} });
Run Code Online (Sandbox Code Playgroud)

注意:用户代理是可欺骗的。IP (REMOTE_ADDR)“不能”伪造,但在少数站点上设置可能会导致问题,例如,如果您在 NGINX 后面,您可能会发现 REMOTE_ADDR 始终包含 NGINX 服务器 IP。

如果您使用的是典型的第 3 方 API,它将提供非敏感信息,直到您达到 API 密钥的使用上限,那么(我认为)上述解决方案应该足够了。