Добро пожаловать в серию « Создай свой стартап с помощью 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
Давайте начнем с рассмотрения ранней безопасности 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 пользователей наверняка.
Как мы можем сделать это более безопасным? Вот несколько идей:
- Никогда не передавайте секретный ключ через Интернет.
- Не передавайте какие-либо данные в виде параметров URL, которые могут отображаться в журналах сервера.
- Подпишите все данные, чтобы проверить их точность.
Это на самом деле стандартные практики, используемые для защиты API.
Примечание. Примером риска передачи данных, которые могут быть представлены в журналах сервера, может быть электронная почта и токен OAuth Facebook. Если они найдены в журналах, они могут быть использованы с API Facebook для доступа к чьей-либо учетной записи Facebook.
Реализация лучшей безопасности API
Использование хэш-подписей
Во-первых, я собираюсь прекратить передачу $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 и нашего общего секретного ключа:
1
|
$signature => 9f6d2f7dd7d674e85eff51f40f5f830787c37d84d4993ac9ccfea2800285bd02
|
Теперь мы можем вызвать API, не передавая секретный ключ. Вместо этого мы передаем подпись хешированных данных выше.
Я использовал Postman для тестирования API, но вы также можете использовать cURL:
Вот код 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, например, через свой мобильный телефон. Таким образом, после регистрации мы можем подписывать вызовы с помощью их индивидуального токена, и нам не нужно передавать ни секретный ключ приложения, ни индивидуальный токен пользователя.
Отправка данных в заголовках HTTPS
Далее мы перенесем отправку данных в заголовки. Вы можете сделать это легко с почтальоном или cURL. Вот почтальон:
А вот и 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!’;
}
|
В заключение
Мы начали сегодня со следующих целей:
- Никогда не передавайте секретный ключ через Интернет.
- Не передавайте какие-либо данные в виде параметров URL, которые могут отображаться в журналах сервера.
- Подпишите все данные, чтобы проверить их точность.
И мы достигли всех этих целей с помощью лишь скромных изменений в нашем API-коде. Было весело вносить эти изменения и видеть, как легко мы можем лучше защитить API. Надеюсь, вам понравилось следить за сегодняшним эпизодом.
Я регулярно отслеживаю комментарии, поэтому, пожалуйста, присоединяйтесь к обсуждению. Вы также можете связаться со мной через Twitter @lookahead_io напрямую. И, конечно же, следите за будущими уроками здесь, в серии « Построение стартапа с помощью PHP» .
Если вы не сделали этого раньше, попробуйте запланировать встречу в Meeting Planner и сообщите мне, что вы думаете. Я особенно ценю особенность запросов.