Деякий час продовжую шукати інформацію про створення типового шаблона для API, за допомогою якого я зміг би працювати з базою даних MySQL використовуючи POST і GET запити. Для цієї мети я встановив Swagger, описав декілька об’єктів. На основі цього була згенерована документація, яку я відобразив на сторінці за допомогою Swagger UI. І наступне питання з яким я зустрівся – безпека власного API.
Після запуску проєкту на домені – будь-хто може зайти на сторінку, поклікати по кнопках, тим самим відправляти запити до моєї MySQL бази даних. В результаті, зможе отримати дані, які в багатьох випадках було б добре тримати в секреті. Також, визиваючи деякі методи цього API користувач може змінити дані чи видалити їх. У навчальних цілях звичайно можна підключити API до тестової бази даних, і раз в якийсь час просто відновлювати базу даних з резервної копії.
Точно з такою ситуацією зустрічаємось і тоді, коли в базі даних зберігаються дані декількох користувачів. І завдання в тому, щоб описати одне API, яким могли б користуватись різні користувачі, але кожен з них мав би доступ тільки до своїх даних.
Одним із рішень цієї проблеми є використання сессій. Коли користувач заходить на сайт з API на рівні php створюється сессія, яка повертається користувачу у вигляді cookie. Браузер зберігає їх і відправляє при кожному запиті до API. Завдяки такій поведінці можна прив’язатись до значення id сессії. Коли php створює нову сесію, можливо в базі даних створити необхідні таблиці і значення, які будуть мати цей id. І будь-які запити до API будуть впливати саме на ці дані – тобто на дані користувача цієї сессії.
Сказано – зроблено. Тим більше, що в багатьох уроках по php на youtube гарно розказується цей механізм і як ним користуватись. Практикується навіть збереження flush повідомлення в сессію, яке видаляється зразу ж, при першому його перегляді. Спочатку мені сподобався такий підхід, і я реалізував його на одному із своїх тестових сайтів. Ідея була така – ZennoPoster перший раз звертається до API, одержує необхідний ідентифікатор. Дальше він виконує необхідні дії, наприклад добавляє якісь дані, потім змінює їх, потім видаляє. Ідентифікатор сессії зберігає у себе в текстовому списку, і при необхідності є можливість звернутись до API від необхідного id, таким чином одержати доступ до потрібної інформації.
І в цілому, мені здається, що це досить безпечний спосіб – тому що якщо хтось зайде на сторінку з API, то йому буде створено автоматично новий id сессії, а значить доступ він одержить тільки до своїх даних. А так, як в базі зберігаються дані, які не несуть якоїсь конкретної цінності (наприклад id, status. timestamp), то і підбирати id, з метою одержати доступ до даних, скоріш за все нікому не знадобиться. Тому, в цілому, цю ідею можна брати і використовувати базу, як тимчасове сховище для даних, наприклад, яке зберігає чергу виконання завданнь, чи логування виконання завдань. Сам факт, що одне таке API може замінити десятки текстових файлів, які колись створювались для кожного проєкта ZennoPoster.
Те що я описав – це процес налаштований саме на ідентифікацію. Тобто, пред’явнику якогось секретного значення сервер повертає необхідну інформацію чи виконує якісь інструкції з обробки даних. Пам’ятаю сервіс faucetbox – на ньому в якості ідентифікатора виступав адрес Bitcoin кошелька, якого було більш ніж достатньо щоб одержувати виплати, переглядати історію і баланс.
Зараз я не використовую сессії php. Основною причиною є те, що коли я попробував працювати в декілька потоків одночасно – виявилось, що php створює фізично файли сессії у папці Temp (в мене Windows 10, на ньому IIS, і вже там сайт на PHP, і мені так зручно, тому що маю прямий доступ до папки сайта програмою ZennoPoster). І в лог php з помилками після такої довготривалої роботи летить дуже багато помилок (php пробує створити в декількох потоках файл сессії з одним і тим самим іменем).
Тому, якщо зараз я би робив дось подібне, то цього разу я не використовував би сессії PHP зовсім. Більше того, я би на рівні PHP не генерував би ідентифікатори. Скоріш за все, я проектував би так, щоб на рівні ZennoPoster був згенерований унікальний GUID, який по своїй природі є стійким до коллізій, або з якоїсь зв’язки даних згенерував би sha256. Після чого добавляв би цей ідентифікатор для кожного запиту до API в заголовки. Можливо, в моєму API був би також один метод, який виконувався би одноразово в якості реєстрації – тобто відправив туди ID, і в базі даних створились би необхідні рядки з даними для цього ID. А також був би метод який видаляв би всі рядки з даними для цього ID.
Також така архітектура доступу до бази даних через публічний API по php сесіях має особливість – кожен хто прийшов і щось поклікав – залишає слід у вигляді нових даних – ось так і появляється інформаційний мусор. Якщо на нього не реагувати – то база даних наповнюється мусором, який важко відділити потім від потрібних даних. Тому його потрібно якось прибирати – в моєму випадку написати додатковий проєкт для ZennoPoster, який раз в якийсь час буде виконувати запити на очистку для тих id, які мені вже не потрібні і анонімні, які я не створював, чи створював в процесі тестування.
У деяких випадках, можна створити декількох користувачів бази даних, наприклад у одного з них зробити доступ тільки для зчитування. І в такому публічному API залишити тільки методи які видають дані всім бажаючим. А наповнювати цю саму базу даних даними можна іншим, приватним API, використовуючи користувача бази даних, у якого є такий доступ. Наприклад, є у нас шаблон ZennoPoster, який раз в хвилину провіряє курс Bitcoin на декількох біржах і добавляє дані в базу. І є у нас публічний API, який видає нам список рядків за останніх 24 години у вигляді JSON, щоб ми могли намалювати графік на якомусь сайті, чи порахувати якусь математику в іншому проєкті ZennoPoster (адже в проекті який повинен рахувати математику не дуже захочеться реалізовувати якусь процедуру реєстрації наприклад, а дані не є якимись секретними, щоб переживати, що до них одержить доступ хтось інший, при цьому хтось інший не зможе добавити туди свої дані чи якось вплинути на них).
Вирішив добавити, що хоча я говорив тут в основному про ідентифікацію, проте на стороні API всерівно проходить процес автентифікації, тобто перевірка того, що для конкретного ідентифікатора в базі даних находиться потрібний ресурс, після чого якщо ресурс присутній, то відбувається авторизація, тобто надається право доступу до інформації, яка належить цьому ідентифікатору, після чого вже і повертаються дані клієнту у необхідному вигляді. Власне доступ до API на основі ідентифікації, це скоріше моє бачення логіки взаємодії, яка описана в цій публікації, яку я виділив таким заголовком, щоб відрізняти її від інших варіантів.