Статьи

Создание вашего стартапа: защита API

Конечный продукт
Что вы будете создавать

Добро пожаловать в серию « Создай свой стартап с помощью PHP» , которая поможет читателям в запуске реального запуска, Meeting Planner . В каждом эпизоде ​​подробно описываются различные проблемы кодирования и бизнеса, а также подробные примеры, которые можно использовать для изучения.

Недавно я познакомил вас с простой генерацией REST API в Yii и новым сервисным API RESTful для Meeting Planner . В то время я упоминал, что эти API были слабо защищены. Конечно, между клиентом и сервером существовал общий секрет, но возникла пара проблем.

Во-первых, секретный ключ и токены пользователя неоднократно передавались в параметрах запроса вызовов SSL. И не было никакой другой проверки подлинности данных, позволяющей атаковать от среднего лица.

В сегодняшнем выпуске я расскажу вам, как я защитил API от этих недостатков для более надежного API.

Если вы читали нашу серию стартапов , вы, вероятно, уже пробовали Meeting Planner и Simple Planner , но если нет, то пожалуйста. Планировать встречу легко:

Как обычно, я буду участвовать в комментариях ниже, поэтому, пожалуйста, поделитесь своими мыслями. Вы также можете связаться со мной в Твиттере @lookahead_io . Я всегда особенно заинтригован, если вы хотите предложить новые функции или темы для будущих уроков.

Напоминаем, что весь код для Meeting Planner написан на Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с нашей параллельной серией Программирование с Yii2 .

Давайте начнем с рассмотрения ранней безопасности API, которую я кодировал. Предположим, есть мобильное приложение, с которым я поделился $app_id и $app_secret . Принимаются только абоненты API с этими ключами.

Например, приложение пытается зарегистрировать своего владельца, скорее всего, нового пользователя Meeting Planner:

1
2
3
4
5
6
7
8
9
public function actionRegister($app_id=», $app_secret=»,
   $source=»,$firstname =»,$lastname=»,
   $email = »,$oauth_token=») {
     Yii::$app->response->format = Response::FORMAT_JSON;
     // verify app_id and app_key
     if (!Service::verifyAccess($app_id,$app_secret)) {
       // to do — error msg
       return false;
     }

Приложение вызывает вышеуказанный actionRegister через https://api.meetingplanner.io/user-token/register/ со следующими аргументами:

  • $app_id и $app_secret для аутентификации
  • $source = 'facebook' для службы OAuth, которую мы используем, и сопровождающей $oauth_token от этой службы
  • $email , $firstname и $lastname предоставляемые через OAuth

Все это аргументы запроса, такие как:

1
https://api.meetingplanner.io/user-token/register/?app_id=777&app_secret=imwithher&source=facebook&oauth_token=zuckerburger&[email protected]&firstname=thomas&lastname=macfarlins

Service::verifyAccess($app_id,$app_secret) ищет ключи для аутентификации вызова, как показано ниже:

01
02
03
04
05
06
07
08
09
10
11
class Service extends Model
{
    public static function verifyAccess($app_id,$app_secret) {
      if ($app_id == Yii::$app->params[‘app_id’]
        && $app_secret == Yii::$app->params[‘app_secret’]) {
            Yii::$app->params[‘site’][‘id’]=SiteHelper::SITE_SP;
            return true;
        } else {
          return false;
        }
      }

Поскольку ключи и данные были отправлены через SSL, они довольно безопасны, но не непобедимы. Кроме того, секретный ключ не является безопасным на iPhone пользователей наверняка.

Как мы можем сделать это более безопасным? Вот несколько идей:

  1. Никогда не передавайте секретный ключ через Интернет.
  2. Не передавайте какие-либо данные в виде параметров URL, которые могут отображаться в журналах сервера.
  3. Подпишите все данные, чтобы проверить их точность.

Это на самом деле стандартные практики, используемые для защиты API.

Примечание. Примером риска передачи данных, которые могут быть представлены в журналах сервера, может быть электронная почта и токен OAuth Facebook. Если они найдены в журналах, они могут быть использованы с API Facebook для доступа к чьей-либо учетной записи Facebook.

Во-первых, я собираюсь прекратить передачу $app_secret . Вместо этого мы подпишем исходящие данные вместе с ним перед вызовом API.

Таким образом, мы разложим переменные по алфавиту и объединим их в строку, например так:

1
$data = $email.$firstname.$lastname.$oauth_token.$source;

В результате чего:

1
$data = ‘[email protected]

Затем мы будем хэшировать данные с помощью PHP hash_hmac и алгоритма sha256 , используя наш секретный ключ.

1
2
$signature = hash_hmac(‘sha256’,
   $data,Yii::$app->params[‘app_secret’]);
Создание вашего API защиты запуска - PHP hash_hmac docs

Это создает уникальный хэш-код на основе аргументов вызова API и нашего общего секретного ключа:

1
$signature => 9f6d2f7dd7d674e85eff51f40f5f830787c37d84d4993ac9ccfea2800285bd02

Теперь мы можем вызвать API, не передавая секретный ключ. Вместо этого мы передаем подпись хешированных данных выше.

Я использовал Postman для тестирования API, но вы также можете использовать cURL:

Создание вашего API защиты стартапа - тестирование API с подписью

Вот код API получения, который ответил на вызов выше:

1
2
3
4
5
6
7
8
public function actionRegister($app_id=», $source=»,$firstname =»,$lastname=»,$email = »,$oauth_token=»,$sig=») {
     Yii::$app->response->format = Response::FORMAT_JSON;
     $sig_target = hash_hmac(‘sha256’,$email.$firstname.$lastname.$oauth_token.$source,Yii::$app->params[‘app_secret’]);
     if ($app_id != Yii::$app->params[‘app_id’] && $sig==$sig_target) {
       return ‘it worked!’;
     } else {
       return ‘failed!’;
     }

Кроме того, как я рассмотрел в прошлый раз , каждый пользователь получает свой собственный токен, когда он получает доступ к Meeting Planner через API, например, через свой мобильный телефон. Таким образом, после регистрации мы можем подписывать вызовы с помощью их индивидуального токена, и нам не нужно передавать ни секретный ключ приложения, ни индивидуальный токен пользователя.

Далее мы перенесем отправку данных в заголовки. Вы можете сделать это легко с почтальоном или cURL. Вот почтальон:

Создание вашего API защиты запуска - Тестирование API, отправляющего данные в заголовках

А вот и CURL:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public function actionCurl($sig) {
     $ch = curl_init();
     curl_setopt($ch, CURLOPT_URL,»http://localhost:8888/mp-api/user-token/register?sig=».$sig);
     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
     $headers = [
       ‘app_id: ‘.’imwithher’,
       ’email: ‘.’[email protected]’,
       ‘firstname: ‘.’thomas’,
       ‘lastname: ‘.’macfarlins’,
       ‘oauth_token: ‘.’zuckerburger’,
       ‘source: ‘.’facebook’,
     ];
     curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
     $server_output = curl_exec ($ch);
     var_dump($server_output);
     curl_close ($ch);
   }

Вот код получения, который получает данные API из заголовков HTTPS:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public function actionRegister($sig=») {
     Yii::$app->response->format = Response::FORMAT_JSON;
     $headers = Yii::$app->request->headers;
     $email= $headers->get(’email’);
     $firstname= $headers->get(‘firstname’);
     $lastname= $headers->get(‘lastname’);
     $oauth_token= $headers->get(‘oauth_token’);
     $source = $headers->get(‘source’);
     if ($headers->has(‘app_id’)) {
       $app_id = $headers->get(‘app_id’);
     }
     $sig_target = hash_hmac(‘sha256’,$email.$firstname.$lastname.$oauth_token.$source,Yii::$app->params[‘app_secret’]);
     if ($app_id != Yii::$app->params[‘app_id’] && $sig==$sig_target) {
       return ‘it worked!’;
     } else {
       return ‘failed!’;
     }

Мы начали сегодня со следующих целей:

  1. Никогда не передавайте секретный ключ через Интернет.
  2. Не передавайте какие-либо данные в виде параметров URL, которые могут отображаться в журналах сервера.
  3. Подпишите все данные, чтобы проверить их точность.

И мы достигли всех этих целей с помощью лишь скромных изменений в нашем API-коде. Было весело вносить эти изменения и видеть, как легко мы можем лучше защитить API. Надеюсь, вам понравилось следить за сегодняшним эпизодом.

Я регулярно отслеживаю комментарии, поэтому, пожалуйста, присоединяйтесь к обсуждению. Вы также можете связаться со мной через Twitter @lookahead_io напрямую. И, конечно же, следите за будущими уроками здесь, в серии « Построение стартапа с помощью PHP» .

Если вы не сделали этого раньше, попробуйте запланировать встречу в Meeting Planner и сообщите мне, что вы думаете. Я особенно ценю особенность запросов.