it-swarm.com.ru

Безопасные вызовы API с AJAX а также PHP к стороннему API

Я хочу сделать вызовы GET, POST и PUT для стороннего API и отобразить ответ на стороне клиента через AJAX. Для вызовов API требуется токен, но мне нужно держать этот токен в секрете, а не в коде JS на стороне клиента.

Я видел несколько предложений вроде этого иметь посередине серверный код, который будет запрашиваться AJAX и обрабатывать фактический вызов API. Я в порядке, работая напрямую с API из AJAX, но я не уверен, как работать с двухэтапным процессом, чтобы скрыть токен от пользователей. Мой Google не нашел никаких указаний на лучший метод достижения этой цели.

В моем случае сервер посередине будет работать на PHP, поэтому я предполагаю, что cURL/Guzzle - это простая опция для выполнения вызовов API с токеном. Ответы API будут JSON.

Может ли кто-нибудь дать мне пример того, как это можно сделать с помощью jQuery.ajax (), PHP, стороннего API?

В качестве альтернативы, если есть какие-либо качественные ресурсы, подробно описывающие этот метод, я был бы признателен за ссылку. Точно так же, если это ужасный метод, было бы здорово узнать, почему.

Правка
Вероятно, стоит отметить, что я хочу максимально развернуть это; он будет использоваться на нескольких сайтах с уникальными конфигурациями, поэтому в идеале это должно быть реализовано без изменения конфигурации учетной записи сервера или хостинга.

8
t-jam

Это немного сложно без примера кода. Но, как я понял, вы можете следовать этому,

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;

 }
}
6
MMRahman

Поскольку все, что вам нужно, это добавить токен в http headers, который, я предполагаю, Authorization, простым способом было бы реализовать прокси-сервер, который выполняет вызовы к вашей конечной точке api после их добавления. Пример файла для nginx будет

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

Это гораздо более разумный подход, нежели написание программы, и вы получаете четыре строчки кода. Обязательно измените ваши параметры соответствующим образом и добавьте другие параметры по мере необходимости для используемого вами 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>
8
georoot

Исходя из ваших требований, это выглядит так: «Серверный код в середине». Релейный (прокси) сценарий - лучший вариант. 

PHP пример здесь . Нотабене для обработки ошибок CURL он возвращает новый «объект», содержащий ['status'] ('OK' или информация о сбое CURL) и ['msg'], содержащий фактический ответ от поставщика API. В вашем JS исходный API-объект "Object" теперь требует извлечения на один уровень ниже "msg".

Основные реле/​​Прокси можно обойти

Если вы используете сценарий ретрансляции, то кто-то ищет ключ API, вероятно попытается в другом месте. Тем не мение; пират мог бы просто заменить свой вызов провайдеру API, используя ваш ключ API, на вызов вашего скрипта (и ваш ключ API все равно будет использоваться).

Запуск сценария AJAX/relay с помощью поисковых роботов

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;

Сценарий ретрансляции и дополнительный код для решения вышеуказанных проблем

Не переусердствуйте с пиратской защитой; реле должно быть быстрым и задерживаться незаметно для посетителей. Возможные решения (не эксперт и не ржавый сессий):

1: PHP сеанс решения

Проверяет, вызывается ли ретранслятор тем, кто посетил вашу страницу AJAX за последние 15 минут, предоставил действительный токен и имеет ли тот же пользовательский агент и 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: опция 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);
}
?>

Relay Script Используйте существующий пример и перед блоком 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 всегда содержит IP-адрес сервера NGINX. 

Если вы используете типичный сторонний API, который будет предоставлять НЕ ЧУВСТВИТЕЛЬНУЮ информацию, пока вы не достигнете предела использования для вашего API-ключа, тогда (я думаю) вышеуказанных решений должно быть достаточно.

4
scytale

Как отмечали люди, вы хотите, чтобы прокси-метод на вашем сервере скрывал API-ключ.

Чтобы избежать неправильного использования вашего метода на сервере, защитите вызов с помощью одноразового токена (как вы обычно используете для форм) - сгенерированного с вашего сервера (не в javascript ..).

Я не фанат вставленного выше кода, который проверяет наличие известных агентов http-пользователя ... или токенов сайта ... это небезопасно.

2
michael - mlc

Если вы используете cUrl, то вы должны защищать свой сервер. Я лично использую Google ReCaptcha, который, безусловно, создан для решения таких проблем, как ваша. Здесь очень хорошо объясняется интеграция на стороне клиента и сервера. https://webdesign.tutsplus.com/tutorials/how-to-integrate-no-captcha-recaptcha-in-your-website--cms-23024 Таким образом, вы не Не нужно ничего менять в ваших файлах виртуального хоста и любых конфигурациях Apache.

1
Luis Gar

Я бы использовал опубликованное решение @MMRahman, если вы хотите добавить слой безопасности между вашим бэкэндом и вашим веб-интерфейсом, что вы можете сделать, когда пользователь, выполнив вход в систему, сгенерирует уникальный идентификатор, сохранит его в сеансе сервера и в файле cookie или локальное/сессионное хранилище браузера, таким образом, когда вы вызываете свой бэкэнд с помощью ajax, вы можете получить значение из того места, где вы сохранили в браузере, и проверить, совпадают ли значения, если да, вы вызываете внешний API и вернуть значения, если не просто игнорировать запрос.

Итак, подведем итоги: Логин пользователя -> создать уникальный идентификатор -> сохранить его в сеансе сервера и сеансе браузера -> сделать вызов из ajax, передав в качестве параметра значение из сеанса браузера -> проверить, совпадает ли это с сохраненным значением сеанса сервера -> если да, вызовите внешний API, используя токен, хранящийся в вашем бэкэнде/db/file/как хотите 

0
Emilio Camacho