Аутентифікація на базі токена REST API


122

Я розробляю API REST, для якого потрібна автентифікація. Оскільки сама аутентифікація відбувається через зовнішню веб-службу через HTTP, я аргументував, що ми видаватимемо маркери, щоб уникнути повторного виклику служби аутентифікації. Що чітко підводить мене до мого першого питання:

Це справді краще, ніж просто вимагати від клієнтів використання HTTP Basic Auth для кожного запиту та кешування викликів на стороні сервера служби аутентифікації?

Рішення Basic Auth має перевагу в тому, що не потрібно проводити повну поїздку до сервера, перш ніж розпочати запити на вміст. Токени потенційно можуть бути більш гнучкими за обсягом (тобто надавати лише права на певні ресурси чи дії), але це здається більш відповідним контексту OAuth, ніж мій простіший варіант використання.

В даний час жетони набуваються так:

curl -X POST localhost/token --data "api_key=81169d80...
                                     &verifier=2f5ae51a...
                                     &timestamp=1234567
                                     &user=foo
                                     &pass=bar"

api_key, timestampІ verifierпотрібно все запити. "Верифікатор" повертається:

sha1(timestamp + api_key + shared_secret)

Мій намір полягає в тому, щоб дозволяти лише дзвінки відомих сторін і не допускати повторного використання дзвінків.

Це досить добре? Недолік? Перевищення?

Маючи жетони в руці, клієнти можуть придбати ресурси:

curl localhost/posts?api_key=81169d80...
                    &verifier=81169d80...
                    &token=9fUyas64...
                    &timestamp=1234567

Для найпростішого можливого дзвінка це видається жахливо багатослівним. З огляду на те, що shared_secretбажання буде вбудовано в (як мінімум) додаток для iOS, з якого я б припустив, що його можна витягти, чи це навіть пропонує щось, що не відповідає помилковому почуттю безпеки?


2
Замість використання sha1 (timestamp + api_key + shard_secret) вам слід використовувати hmac (shared_secret, timpestamp + api_key) для кращого хешування безпеки en.wikipedia.org/wiki/Hash-based_message_authentication_code
Мігель А. Карраско

@ MiguelA.Carrasco І, схоже, є консенсус 2017 року, що bCrypt - це новий інструмент хешування.
Шон

Відповіді:


94

Дозвольте мені розділити все і вирішити підхід до кожної проблеми ізольовано:

Аутентифікація

Для аутентифікації, baseauth має перевагу в тому, що це зріле рішення на рівні протоколу. Це означає, що для вас вже вирішено багато проблем, які можуть «з’явитися пізніше» . Наприклад, із BaseAuth, користувацькі агенти знають, що пароль - це пароль, тому вони не кешують його.

Автентичне завантаження сервера

Якщо ви видаєте маркер користувачеві замість кешування автентифікації на вашому сервері, ви все одно робите те саме: Кешування інформації про автентифікацію. Єдина відмінність полягає в тому, що ви покладаєте відповідальність за кешування на користувача. Користувачеві це здається непотрібним трудом без жодних прибутків, тому я рекомендую обробляти це прозоро на своєму сервері, як ви запропонували.

Безпека передачі

Якщо ви можете використовувати SSL-з'єднання, це все, що є, з'єднання захищене *. Щоб запобігти випадковому багаторазовому виконанню, ви можете відфільтрувати кілька URL-адрес або попросити користувачів включити до URL-адреси випадковий компонент ("nonce").

url = username:key@myhost.com/api/call/nonce

Якщо це неможливо, а передана інформація не є секретною, рекомендую закріпити запит хешем, як ви запропонували в підході маркера. Оскільки хеш забезпечує безпеку, ви можете доручити своїм користувачам надати хеш як пароль baseauth. Для поліпшення надійності я рекомендую використовувати випадкову рядок замість часової позначки як "nonce", щоб запобігти повторним атакам (протягом однієї секунди можуть бути зроблені два легітимні запити). Замість того, щоб надати окремі поля "загальний секрет" та "ключ апі", ви можете просто використовувати ключ api як загальний секрет, а потім використовувати сіль, яка не змінюється, щоб запобігти атакам райдужної таблиці. Поле ім'я користувача є гарним місцем для того, щоб не помістити його, оскільки воно є частиною автентики. Отже, тепер у вас є такий чистий дзвінок:

nonce = generate_secure_password(length: 16);
one_time_key = nonce + '-' + sha1(nonce+salt+shared_key);
url = username:one_time_key@myhost.com/api/call

Це правда, що це трохи трудомістко. Це тому, що ви не використовуєте рішення рівня протоколу (наприклад, SSL). Тому може бути хорошою ідеєю надати якісь SDK користувачам, щоб принаймні їм не потрібно було самі проходити це. Якщо вам потрібно зробити це таким чином, я вважаю, що рівень безпеки є відповідним (точно-праворуч).

Безпечне таємне зберігання

Це залежить від того, кого ви намагаєтеся зірвати. Якщо ви забороняєте людям, які мають доступ до телефону користувача, користуватися послугою REST від імені користувача, тоді було б непогано знайти якийсь API для введення ключів у цільовій ОС і мати SDK (або реалізатор) для зберігання ключ там. Якщо це неможливо, ви можете, принаймні, трохи складніше отримати секрет, зашифрувавши його, а також зберігати зашифровані дані та ключ шифрування в окремих місцях.

Якщо ви намагаєтеся не дозволити іншим постачальникам програмного забезпечення отримувати ключ API для запобігання розвитку альтернативних клієнтів, майже тільки підхід шифрування та зберігання окремо працює. Це криптовалюта, і на сьогоднішній день ніхто не придумав справді безпечного рішення проблем цього класу. Найменше, що ви можете зробити, - це все-таки видавати один ключ для кожного користувача, щоб ви могли заборонити зловживання ключами.

(*) EDIT: З'єднання SSL більше не слід вважати безпечними, не роблячи додаткових кроків для їх перевірки .


Дякую cmc, всі хороші бали та чудова їжа для роздумів. У кінцевому підсумку я застосував підхід токена / HMAC, подібний до того, про який ви говорили вище, а не як механізм аутентифікації S3 REST API .
кантлін

Якщо ви кешуєте маркер на сервері, то чи не є він по суті таким же старим хорошим ідентифікатором сеансу? Ідентифікатор сесії недовговічний і він також додається до швидкого зберігання кешу (якщо ви його реалізуєте), щоб уникнути потрапляння ваших БД на кожен запит. У справжньому дизайні RESTful та без громадянства не повинно бути сеансів, але якщо ви використовуєте маркер як ідентифікатор, а потім все одно натискаєте на БД, то чи не буде краще просто використовувати ідентифікатор сесії? Крім того, ви можете перейти до веб-маркерів JSON, які містять зашифровану або підписану інформацію для даних про весь сеанс для справжнього дизайну без апарату.
JustAMartin

16

Чистий API RESTful повинен використовувати основні стандартні протоколи:

  1. Для HTTP API RESTful повинен відповідати існуючим стандартним заголовкам HTTP. Додавання нового заголовка HTTP порушує принципи REST. Не вигадуйте колесо заново, використовуйте всі стандартні функції у стандартах HTTP / 1.1 - включаючи коди відповіді стану, заголовки тощо. Веб-сервіси RESTFul повинні використовувати і покладатися на стандарти HTTP.

  2. ОРГАНІЗАЦІЙНІ послуги ОБОВ'ЯЗКОВО бути СТАТЕЛЬНИМ Будь-які хитрощі, наприклад аутентифікація на основі лексеми, яка намагається запам'ятати стан попередніх REST-запитів на сервері, порушує принципи REST. Знову ж таки, це ОБОВ'ЯЗКОВО; тобто якщо веб-сервер зберігає на сервері будь-яку інформацію про контекст запиту / відповіді, намагаючись встановити будь-який сеанс на сервері, то ваша веб-служба НЕ є без громадянства. І якщо це НЕ без громадянства, це НЕ БЕЗПЕЧНО.

Підсумок: для цілей аутентифікації / авторизації слід використовувати стандартний заголовок авторизації HTTP. Тобто слід додати заголовок авторизації / автентифікації HTTP у кожен наступний запит, який потребує автентифікації. API REST повинен відповідати стандартам схеми автентифікації HTTP. Особливості того, як цей формат повинен бути відформатований, визначені в стандартах RFC 2616 HTTP 1.1 - розділ 14.8 Авторизація RFC 2616 та в аутентифікації HTTP RFC 2617: Автентифікація базового та дайджестного доступу .

Я розробив послугу RESTful для програми Cisco Prime Performance Manager. Шукайте в Google документ REST API, який я написав для цієї програми, для більш детальної інформації про відповідність API RESTFul тут . У цій реалізації я вирішив використовувати схему авторизації HTTP "Basic". - перевірте версію 1.5 або вище цього документа REST API та шукайте авторизацію в документі.


8
"Додавання нового заголовка HTTP порушує принципи REST" Як це? І якщо ви знаходитесь біля цього, ви можете так люб'язно пояснити, в чому саме полягає різниця (щодо принципів) між паролем, який закінчується через певний період, і маркером, який закінчується через певний період.
кращий олівер

6
Ім'я користувача + пароль - це маркер (!), Який обмінюється між клієнтом та сервером у кожному запиті. Цей маркер підтримується на сервері і має час жити. Якщо термін дії терміну дії закінчився, я повинен придбати новий. Ви, схоже, асоціюєте "маркер" з "сеансом на сервері", але це невірний висновок. Це навіть не має значення, оскільки це була б деталізація щодо реалізації. Ваша класифікація маркерів, крім імені користувача / пароля, як державних, суто штучна, імхо.
кращий олівер

1
Я думаю, ви повинні мотивувати, чому робити імплементацію з RESTful над базовою автентифікацією, що є частиною оригінального питання. Можливо, ви також можете посилатися на кілька хороших прикладів із включеним кодом. Як початківець у цій темі теорія здається достатньо зрозумілою з великою кількістю хороших ресурсів, але методу впровадження немає, і приклади є спільними. Мені здається неприємним, що, здається, потрібне спеціальне кодування, щоб своєчасно реалізувати те, що було зроблено тисячі разів.
JPK

13
-1 "Будь-які хитрощі, такі як аутентифікація на основі лексеми, яка намагається запам'ятати стан попередніх REST-запитів на сервері, порушує принципи REST." Аутентифікація на основі лексеми не має нічого спільного зі станом попередніх REST-запитів і не порушує безгромадянськість REST .
Керем Байдоган

1
Отже, відповідно до цього, веб-маркери JSON є порушенням REST, оскільки вони можуть зберігати стан користувача (претензії)? У будь-якому разі я вважаю за краще порушувати REST і використовувати старий хороший ідентифікатор сеансу як "маркер", але початкова автентифікація проводиться з ім'ям користувача + пропуск, підписаним або зашифрованим за допомогою спільної таємної та дуже короткотривалої часової позначки (тому це не вдається, якщо хтось намагається повторно відтворити що). У додатку "підприємство-ish" важко відкинути переваги сеансу (уникаючи попадання в базу даних для деяких даних, необхідних майже в кожному запиті), тому іноді доводиться жертвувати справжньою безгромадянністю.
JustAMartin

2

У Інтернеті загрозливий протокол базується на тимчасовому маркуванні, який обмінюється між браузером та сервером (за допомогою заголовка файлів cookie чи перезапису URI) на кожен запит. Цей маркер зазвичай створюється на серверному кінці, і це фрагмент непрозорих даних, який має певний час життя, і є єдиною метою визначення конкретного агента веб-користувачів. Тобто маркер є тимчасовим і стає ДЕРЖАВНИМ, який веб-сервер повинен підтримувати від імені клієнтського агента користувача протягом тривалості цієї розмови. Тому спілкування за допомогою маркера таким чином СТАТЕФУЛЬНО. І якщо розмова між клієнтом та сервером є СТАТЕФУЛЬНОЮ, це не ВИБУДОВО.

Ім’я користувача / пароль (надсилається в заголовку Авторизації) зазвичай зберігається в базі даних з метою ідентифікації користувача. Іноді користувач може означати іншу програму; однак ім'я користувача / пароль НІКОЛИ не призначений для ідентифікації конкретного агента користувача веб-клієнта. Бесіда між веб-агентом та сервером на основі використання імені користувача / пароля в заголовку авторизації (після базового авторизації HTTP) є СТАТЕЛЕСНОЮ, оскільки передній край веб-сервера не створює і не підтримує ДЕРЖАВНИЙ інформаціющо б то не було від імені конкретного агента користувача веб-клієнта. І виходячи з мого розуміння REST, протокол чітко зазначає, що розмова між клієнтами та сервером повинна бути СТАТЕЛЕСНОЮ. Тому, якщо ми хочемо мати справжню послугу RESTful, ми повинні використовувати ім'я користувача / пароль (див. RFC, згаданий у моєму попередньому дописі) у заголовку авторизації для кожного окремого дзвінка, а не токена типу маркера (наприклад, секери-маркери, створені на веб-серверах , Маркери OAuth, створені на серверах авторизації тощо).

Я розумію, що кілька викликаних REST-провайдерів використовують маркери, такі як OAuth1 або OAuth2, приймають-жетони, щоб передаватись як "Авторизація: Носій" у заголовках HTTP. Однак мені здається, що використання цих жетонів для послуг RESTful порушить справжню СТАТЕЛЬНІСТЬ, тобто REST охоплює; оскільки ці маркери - це тимчасовий фрагмент даних, створений / підтримуваний на стороні сервера для ідентифікації конкретного агента користувача веб-клієнта протягом дійсної тривалості розмови про цей клієнт / сервер. Тому будь-яка служба, яка використовує ці маркери OAuth1 / 2, не повинна називатися REST, якщо ми хочемо дотримуватися значення ІСТИННОГО протоколу STATELESS.

Рубенс

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.