繁体   English   中英

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

[英]Secure API calls with AJAX and PHP to 3rd party API

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

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

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

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

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

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

因为你想要的只是将标记添加到http headers ,我假设是Authorization一种简单的方法是实现一个代理服务器,在添加它们之后调用你的api端点。 nginx的示例文件将是

location /apiProxy {
    proxy_pass http://www.apiendPoint.com/;
    proxy_set_header Authorization <secret token>;
}

这是一种更聪明的方法,而不是编写程序,并使用4行代码让您失望。 确保相应地更改参数,并根据您正在使用的api客户端的需要添加其他参数。 javascript方面的唯一区别是使用location url而不是服务提供的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>

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

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;

 }
}

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

PHP示例在这里 NB处理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和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);} });

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

使用现有的示例中继脚本 ,在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);
}
?>

中继脚本使用现有示例 ,在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密钥的使用上限,那么(我认为)上述解决方案就足够了。

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM