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

时间:2018-02-23 12:11:12

标签: javascript php jquery ajax php-curl

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

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

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

有谁能请给我一个粗略的例子,说明如何使用jQuery.ajax(),PHP,第三方API实现这一目标?

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

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

6 个答案:

答案 0 :(得分:8)

因为您只想将令牌添加到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>

答案 1 :(得分:7)

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

AJAX CALL

$.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 );

示例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;

 }
}

答案 2 :(得分:4)

根据您的要求,看起来“中间的服务器端代码”中继(代理)脚本是最佳选择。

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

可以规避基本的中继/代理

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

搜索引擎机器人运行您的AJAX /中继脚本

谷歌机器人(其他人?)执行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;

针对上述问题的中继脚本和其他代码

不要过分保护海盗;继电器应该快速并且延迟访客不会注意到。可能的解决方案(没有专家和会议生锈):

1: PHP会话解决方案

检查在过去15分钟内访问过您的AJAX页面的人是否调用了中继,提供了有效的令牌,并且具有相同的用户代理和IP地址。

您的Ajax页面将以下代码段添加到您的PHP&amp; 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);} });

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

使用existing example relay script并在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和同一域上的页面/中继(可能的工作)。会话可能会影响性能。如果网站已经使用了会话,则代码需要考虑这一点。

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

中继脚本使用existing example并在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)“不能”伪造,但在少数网站上设置可能会导致问题,例如如果您在NGINX后面,您可能会发现REMOTE_ADDR始终包含NGINX服务器IP。

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

答案 3 :(得分:2)

正如人们所指出的那样,您希望服务器上的代理方法隐藏API密钥。

为避免在服务器上滥用您的方法,请使用一次性令牌保护呼叫(就像您通常用于表单一样) - 从您的服务器生成(不是在javascript中)。

我不是上面编码粘贴的粉丝,它会检查已知的http用户代理...或网站令牌......这不安全。

答案 4 :(得分:1)

如果使用cUrl,则必须保护您的服务器。我个人使用的方式是谷歌reCaptcha,肯定是为安排像你的问题。很好地解释了客户端和服务器端的集成在这里一步一步。 https://webdesign.tutsplus.com/tutorials/how-to-integrate-no-captcha-recaptcha-in-your-website--cms-23024 通过这种方式,您无需更改虚拟主机文件和任何apache配置中的任何内容。

答案 5 :(得分:1)

我会使用发布的解决方案@MMRahman,如果你想在你的后端和你的前端之间添加一个安全层,那么当用户登录时生成一个唯一的ID,将它存储在服务器会话中并在浏览器的cookie或本地/会话存储,这样当你使用ajax调用后端时,你可以从浏览器中存储的地方获取值,并检查值是否相同,如果是,则调用外部api并返回值,如果不是忽略请求。

总结: 用户登录 - &gt;生成唯一ID - &gt;将其存储在服务器会话和浏览器会话中 - &gt;从ajax调用传递作为参数传递来自浏览器会话的值 - &gt;检查它是否与服务器会话存储值匹配 - &gt;如果是,则使用存储在后端/ db / file /中的令牌调用外部api