Разработка сайтов в Дебальцево, ДНР. Авторизация PHP с помощью JWT (веб-токены JSON)

 
 

Было время, когда единственным способом аутентифицировать себя с помощью приложения было предоставление своих учетных данных (обычно имя пользователя или адрес электронной почты и пароль), а затем использовался сеанс для сохранения состояния пользователя до выхода пользователя из системы. Чуть позже мы начали использовать API аутентификации. А в последнее время JWT или веб-токены JSON все чаще используются в качестве еще одного способа аутентификации запросов к серверу.

В этой статье вы узнаете, что такое JWT и как использовать их с PHP для выполнения аутентифицированных пользовательских запросов.

JWT против сеансов

Но, во-первых, почему сеансы не так уж хороши? Итак, основных причин три:

Данные хранятся в виде простого текста на сервере.

Несмотря на то, что данные обычно не хранятся в общей папке, любой, у кого есть достаточный доступ к серверу, может прочитать содержимое файлов сеанса.

Они включают запросы на чтение/запись файловой системы.

Каждый раз, когда начинается сеанс или его данные изменяются, серверу необходимо обновить файл сеанса. То же самое происходит каждый раз, когда приложение отправляет файл cookie сеанса. Если у вас большое количество пользователей, вы можете столкнуться с медленным сервером, если не используете альтернативные варианты хранения сеансов, такие как Memcached и Redis.

Распределенные/кластерные приложения.

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

JWT

Теперь давайте начнем изучать JWT. Спецификация JSON Web Token (RFC 7519) была впервые опубликована 28 декабря 2010 г. и последний раз обновлялась в мае 2015 г.

JWT имеют много преимуществ по сравнению с ключами API, в том числе:

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

JWT не требуют централизованного органа выдачи или отзыва.

JWT совместимы с OAUTH2.

Данные JWT можно проверить.

JWT имеют средства контроля истечения срока действия.

JWT предназначены для сред с ограниченным пространством, таких как заголовки авторизации HTTP.

Данные передаются в формате JavaScript Object Notation (JSON).

JWT представлены с использованием кодировки Base64url.

Как выглядит JWT?

Вот пример JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E

На первый взгляд кажется, что строка представляет собой просто случайные группы символов, объединенных точкой или точкой. Таким образом, может показаться, что он не сильно отличается от ключа API. Однако, если вы присмотритесь, то увидите три отдельные строки.

Заголовок JWT

Первая строка — это заголовок JWT. Это строка JSON с кодировкой Base64 и URL-адресом. Он указывает, какой криптографический алгоритм использовался для создания подписи, и тип токена, который всегда имеет значение JWT. Алгоритм может быть как симметричным, так и асимметричным.

Симметричный алгоритм использует один ключ как для создания, так и для проверки токена. Ключ распределяется между создателем JWT и его потребителем. Очень важно убедиться, что только создатель и потребитель знают секрет. В противном случае любой может создать действительный токен.

Асимметричный алгоритм использует закрытый ключ для подписи токена и открытый ключ для его проверки. Эти алгоритмы следует использовать, когда общий секрет нецелесообразен или другим сторонам нужно только проверить целостность токена.

Полезная нагрузка JWT

Вторая строка — это полезная нагрузка JWT. Это также строка JSON в кодировке Base64, URL-адрес. Он содержит несколько стандартных полей, которые называются «претензиями». Существует три типа претензий: зарегистрированные, общедоступные и частные.

Зарегистрированные претензии предопределены. Вы можете найти их список в RFC JWT. Вот некоторые из часто используемых:

iat: временная метка выдачи токена.

key: уникальная строка, которую можно использовать для проверки токена, но которая противоречит центральному органу эмитента.

iss: строка, содержащая имя или идентификатор эмитента. Может быть доменным именем и может использоваться для сброса токенов из других приложений.

nbf: метка времени, когда токен должен считаться действительным. Должно быть равно или больше iat.

exp: отметка времени, когда токен должен перестать быть действительным. Должно быть больше iatи nbf.

Публичные претензии можно определить по своему усмотрению. Однако они не могут совпадать с зарегистрированными заявками или заявками на уже существующие публичные заявки. Вы можете создавать частные претензии по желанию. Они предназначены только для использования между двумя сторонами: производителем и потребителем.

Подпись JWT

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

Подпись JWT представляет собой комбинацию трех вещей:

заголовок JWT

полезная нагрузка JWT

секретное значение

Эти три подписаны цифровой подписью (не зашифрованы) с использованием алгоритма, указанного в заголовке JWT. Если мы расшифруем приведенный выше пример, у нас будут следующие строки JSON:

Заголовок JWT

{

«alg»: «HS256»,

«typ»: «JWT»

}

Данные JWT

{

«iat»: 1416929109,

«jti»: «aa7f8d0a95c»,

«scopes»: [

«repo»,

«public_repo»

]

}

Попробуйте сами jwt.io, где вы сможете поиграть с кодированием и декодированием собственных JWT.

Давайте использовать JWT в приложении на основе PHP

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

Существует множество способов интеграции JWT, но вот как мы собираемся это сделать.

Все запросы к приложению, за исключением страницы входа и выхода, должны быть аутентифицированы через JWT. Если пользователь делает запрос без JWT, он будет перенаправлен на страницу входа.

После того, как пользователь заполнит и отправит форму входа, форма будет отправлена ​​через JavaScript в конечную точку входа authenticate.phpв наше приложение. Затем конечная точка извлечет учетные данные (имя пользователя и пароль) из запроса и проверит их действительность.

Если это так, он сгенерирует JWT и отправит его обратно клиенту. Когда клиент получает JWT, он сохраняет его и использует при каждом последующем запросе к приложению.

В упрощенном сценарии пользователь может запросить только один ресурс — файл PHP с подходящим названием resource.php. Это мало что даст, просто вернет строку, содержащую текущую метку времени на момент запроса.

Есть несколько способов использовать JWT при выполнении запросов. В нашем приложении JWT будет отправлен в заголовке авторизации Bearer.

Если вы не знакомы с авторизацией носителя, это форма аутентификации HTTP, при которой токен (например, JWT) отправляется в заголовке запроса. Сервер может проверить токен и определить, следует ли предоставить доступ «носителю» токена.

Вот пример заголовка:

Authorization: Bearer ab0dde18155a43ee83edba4a4542b973

Для каждого запроса, полученного нашим приложением, PHP попытается извлечь токен из заголовка Bearer. Если он присутствует, он затем проверяется. Если он действителен, пользователь увидит обычный ответ на этот запрос. Однако, если JWT недействителен, пользователю не будет разрешен доступ к ресурсу.

Обратите внимание, что JWT не предназначен для замены файлов cookie сеанса.

Предпосылки

Для начала нам нужно установить PHP и Composer в наших системах.

В корне проекта запустите composer install. Это потребует Firebase PHP-JWT, сторонней библиотеки, упрощающей работу с JWT, а также laminas-config, предназначенной для упрощения доступа к данным конфигурации внутри приложений.

Форма входа

Пример формы входа с использованием HTML и JavaScript

Установив библиотеку, давайте пройдемся по коду входа в authenticate.php. Сначала мы делаем обычную настройку, гарантируя, что сгенерированный Composer автозагрузчик доступен.

<? php

declare (strict_types=1) ;

use Firebase\JWT\JWT;

require_once ('.../vendor/autoload.php’) ;

После получения отправки формы учетные данные проверяются в базе данных или другом хранилище данных. Для целей этого примера мы предположим, что они допустимы и установлены $hasValidCredentialsв значение true.

<? php

// extract credentials from the request

if ($hasValidCredentials) {

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

Еще одна вещь, на которую стоит обратить внимание, это то, что $secretKeyэто не будет инициализировано таким образом. Скорее всего, вы установите его в среде и извлечете с помощью библиотеки, такой как phpdotenv, или в файле конфигурации. Я избегал этого в этом примере, так как хочу сосредоточиться на коде JWT.

Никогда не разглашайте его и не храните под контролем версий!

$secretKey = 'bGS6lzFqvvSQ8ALbOxatm7/Vk7mLQyzqaS34Q4oR1ew=';

$issuedAt = new DateTimeImmutable () ;

$expire = $issuedAt→modify ('+6 minutes’) →getTimestamp () ; // Add 60 seconds

$serverName = «your.domain.name»;

$username = «username»; // Retrieved from filtered POST data

$data = [

'iat’ => $issuedAt→getTimestamp (), // Issued at: time when the token was generated

'iss’ => $serverName, // Issuer

'nbf’ => $issuedAt→getTimestamp (), // Not before

'exp’ => $expire, // Expire

'userName’ => $username, // User name

];

Когда данные полезной нагрузки готовы к работе, мы используем статический метод php-jwt encodeдля создания JWT.

Метод:

преобразует массив в JSON

производить заголовки

подписывает полезную нагрузку

кодирует последнюю строку

Он принимает три параметра:

информация о полезной нагрузке

секретный ключ

алгоритм, используемый для подписи токена

При вызове echoрезультата функции возвращается сгенерированный токен:

<? php

// Encode the array to a JWT string.

echo JWT: encode (

$data,

$secretKey,

'HS512'

) ;

}

Использование JWT

Получение ресурса с помощью JavaScript и JWT

Теперь, когда у клиента есть токен, вы можете сохранить его с помощью JavaScript или любого другого механизма, который вы предпочитаете. Вот пример того, как это сделать с помощью ванильного JavaScript. В index.html, после успешной отправки формы возвращенный JWT сохраняется в памяти, форма входа скрыта, и отображается кнопка для запроса метки времени:

const store = {};

const loginButton = document.querySelector ('#frmLogin’) ;

const btnGetResource = document.querySelector ('#btnGetResource’) ;

const form = document.forms[0];

// Inserts the jwt to the store object

store.setJWT = function (data) {

this.JWT = data;

};

loginButton.addEventListener ('submit’, async (e) => {

e.preventDefault () ;

const res = await fetch ('/authenticate.php’, {

method: 'POST’,

headers: {

'Content-type’: 'application/x-www-form-urlencoded; charset=UTF-8'

},

body: JSON.stringify ({

username: form.inputEmail.value,

password: form.inputPassword.value

})

}) ;

if (res.status ≥ 200 && res.status ≤ 299) {

const jwt = await res.text () ;

store.setJWT (jwt) ;

frmLogin.style.display = 'none’;

btnGetResource.style.display = 'block’;

} else {

// Handle errors

console.log (res.status, res.statusText) ;

}

}) ;

Использование JWT

При нажатии на кнопку «Получить текущую временную метку» делается GET-запрос к resource.php, который устанавливает JWT, полученный после аутентификации, в заголовке Authorization.

btnGetResource.addEventListener ('click’, async (e) => {

const res = await fetch ('/resource.php’, {

headers: {

'Authorization’: `Bearer ${store.JWT}`

}

}) ;

const timeStamp = await res.text () ;

console.log (timeStamp) ;

}) ;

Когда мы нажимаем кнопку, выполняется запрос, подобный следующему:

GET /resource.php HTTP/1.1

Host: yourhost.com

Connection: keep-alive

Accept: */*

X-Requested-With: XMLHttpRequest

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0MjU1ODg4MjEsImp0aSI6IjU0ZjhjMjU1NWQyMjMiLCJpc3MiOiJzcC1qd3Qtc2ltcGxlLXRlY25vbTFrMy5jOS5pbyIsIm5iZiI6MTQyNTU4ODgyMSwiZXhwIjoxNDI1NTkyNDIxLCJkYXRhIjp7InVzZXJJZCI6IjEiLCJ1c2VyTmFtZSI6ImFkbWluIn19.HVYBe9xvPD8qt0wh7rXI8bmRJsQavJ8Qs29yfVbY-A0

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

Проверка JWT

Наконец, давайте посмотрим, как мы можем проверить токен в PHP. Как всегда, мы включили автозагрузчик Composer. Затем мы могли бы, при желании, проверить, был ли использован правильный метод запроса. Я пропустил код, чтобы сделать это, чтобы сосредоточиться на коде, специфичном для JWT:

<? php

chdir (dirname (__DIR__));

require_once ('.../vendor/autoload.php’) ;

// Do some checking for the request method here, if desired.

Затем код попытается извлечь токен из заголовка Bearer. Я сделал это, используя preg_match. Если вы не знакомы с этой функцией, она выполняет сопоставление строки с регулярным выражением.

Регулярное выражение, которое я здесь использовал, попытается извлечь токен из заголовка Bearer и сбросить все остальное. Если он не найден, возвращается неверный запрос HTTP 400:

if (! preg_match ('/Bearer\s (\S+) /', $_SERVER['HTTP_AUTHORIZATION’], $matches)) {

header ('HTTP/1.0 400 Bad Request’) ;

echo 'Token not found in request’;

exit;

}

Обратите внимание, что по умолчанию Apache не передает заголовок HTTP_AUTHORIZATIONв PHP. Причиной этого является:

Базовый заголовок авторизации является безопасным только в том случае, если ваше соединение выполняется через HTTPS, поскольку в противном случае учетные данные отправляются в закодированном виде (не зашифрованном) по сети, что является серьезной проблемой безопасности.

Я полностью понимаю логику этого решения. Однако, чтобы избежать путаницы, добавьте следующее в конфигурацию Apache. Тогда код будет работать так, как ожидалось. Если вы используете NGINX, код должен работать так, как ожидалось:

RewriteEngine On

RewriteCond%{HTTP: Authorization} ^ (. +) $

RewriteRule. * — [E=HTTP_AUTHORIZATION:%{HTTP: Authorization}]

Затем мы пытаемся извлечь соответствующий JWT, который будет во втором элементе $matchesпеременной. Если он недоступен, то JWT не был извлечен, и возвращается неверный запрос HTTP 400:

$jwt = $matches[1];

if (! $jwt) {

// No token was able to be extracted from the authorization header

header ('HTTP/1.0 400 Bad Request’) ;

exit;

}

Если мы дойдем до этого момента, JWT был извлечен, поэтому мы переходим к этапу декодирования и проверки. Для этого нам снова понадобится наш секретный ключ, который будет извлечен из среды или конфигурации приложения. Затем мы используем статический метод php-jwt decode, передавая ему JWT, секретный ключ и массив алгоритмов для декодирования JWT.

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

$secretKey = 'bGS6lzFqvvSQ8ALbOxatm7/Vk7mLQyzqaS34Q4oR1ew=';

$token = JWT: decode ($jwt, $secretKey, ['HS512']) ;

$now = new DateTimeImmutable () ;

$serverName = «your.domain.name»;

if ($token→iss≠= $serverName ||

$token→nbf > $now→getTimestamp () ||

$token→exp < $now→getTimestamp ())

{

header ('HTTP/1.1 401 Unauthorized’) ;

exit;

}

Если токен недействителен, например, из-за того, что срок действия токена истек, пользователю будет отправлен заголовок HTTP 401 Unauthorized, и сценарий завершится.

Если процесс декодирования JWT не удался, это может быть так:

Количество предоставленных сегментов не соответствовало стандартным трем, как описано ранее.

Заголовок или полезные данные не являются допустимой строкой JSON.

Подпись недействительна, значит данные были подделаны!

Утверждение nbfустанавливается в JWT с отметкой времени, когда текущая отметка времени меньше этой.

Утверждение iatустанавливается в JWT с отметкой времени, когда текущая отметка времени меньше этой.

Утверждение expустанавливается в JWT с меткой времени, когда текущая метка времени больше этой.

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

Если процесс декодирования и проверки завершится успешно, пользователю будет разрешено сделать запрос, и ему будет отправлен соответствующий ответ.

В заключение

Это краткое введение в веб-токены JSON или JWT и их использование в приложениях на основе PHP. С этого момента вы можете попытаться внедрить JWT в свой следующий API, возможно, попробовав другие алгоритмы подписи, использующие асимметричные ключи, такие как RS256, или интегрировав его в существующий сервер аутентификации OAUTH2 в качестве ключа API.

3D-печать5GABC-анализAndroidAppleAppStoreAsusCall-центрChatGPTCRMDellDNSDrupalExcelFacebookFMCGGoogleHuaweiInstagramiPhoneLinkedInLinuxMagentoMicrosoftNvidiaOpenCartPlayStationPOS материалPPC-специалистRuTubeSamsungSEO-услугиSMMSnapchatSonyStarlinkTikTokTwitterUbuntuUp-saleViasatVPNWhatsAppWindowsWordPressXiaomiYouTubeZoomАвдеевкаАктивные продажиАкцияАлександровск ЛНРАлмазнаяАлчевскАмвросиевкаАнализ конкурентовАнализ продажАнтимерчандайзингАнтрацитАртемовскАртемовск ЛНРАссортиментная политикаБелгородБелицкоеБелозерскоеБердянскБизнес-идеи (стартапы)БрендБрянкаБукингВахрушевоВендорВидеоВикипедияВирусная рекламаВирусный маркетингВладивостокВнутренние продажиВнутренний маркетингВолгоградВолновахаВоронежГорловкаГорнякГорскоеДебальцевоДебиторкаДебиторская задолженностьДезинтермедитацияДзержинскДивизионная система управленияДизайнДимитровДирект-маркетингДисконтДистрибьюторДистрибьюцияДобропольеДокучаевскДоменДружковкаЕкатеринбургЕнакиевоЖдановкаЗапорожьеЗимогорьеЗолотоеЗоринскЗугрэсИжевскИловайскИрминоКазаньКалининградКировскКировскоеКомсомольскоеКонстантиновкаКонтент-маркетингКонтент-планКопирайтингКраматорскКрасноармейскКрасногоровкаКраснодарКраснодонКраснопартизанскКрасный ЛиманКрасный ЛучКременнаяКураховоКурскЛисичанскЛуганскЛутугиноМакеевкаМариупольМаркетингМаркетинговая информацияМаркетинговые исследованияМаркетинговый каналМаркетинг услугМаркетологМарьинкаМедиаМелекиноМелитопольМенеджментМерчандайзерМерчандайзингМиусинскМолодогвардейскМоскваМоспиноНижний НовгородНиколаевНиколаевкаНишевой маркетингНовоазовскНовогродовкаНоводружескНовосибирскНумерическая дистрибьюцияОдессаОмскОтдел маркетингаПартизанский маркетингПервомайскПеревальскПетровскоеПлата за кликПоисковая оптимизацияПопаснаяПравило ПаретоПривольеПрогнозирование продажПродвижение сайтов в ДонецкеПроизводство видеоПромоПромоушнПрямой маркетингРабота для маркетологаРабота для студентаРазработка приложенийРаспродажаРегиональные продажиРекламаРеклама на асфальтеРемаркетингРетро-бонусРибейтРитейлРовенькиРодинскоеРостов-на-ДонуРубежноеСамараСанкт-ПетербургСаратовСватовоСвердловскСветлодарскСвятогорскСевастопольСеверодонецкСеверскСедовоСейлз промоушнСелидовоСимферопольСинергияСколковоСлавянскСнежноеСоздание сайтов в ДонецкеСоледарСоциальные сетиСочиСтаробельскСтаробешевоСтахановСтимулирование сбытаСуходольскСчастьеТелемаркетингТельмановоТираспольТорговый представительТорезТрейд маркетингТрейд промоушнТюменьУглегорскУгледарУкраинскХабаровскХарцызскХерсонХостингЦелевая аудиторияЦифровой маркетингЧасов ЯрЧелябинскШахтерскЮжно-СахалинскЮнокоммунаровскЯндексЯсиноватая