Глобальная авторизация в веб-системах

Материал из YourcmcWiki
Перейти к: навигация, поиск

…или как реализовать простой Single Sign-on в веб-системах.

Ниже описан простейший протокол, который даёт возможность нам сказать внешней системе, кто к нам вошёл, так, что внешняя система знает, что это говорим ей именно мы, а мы знаем, что мы говорим это именно ей.

  • (П) Пользователь.
  • (С1) Система 1 — клиент глобальной авторизации. В неё пришёл пользователь без авторизации.
  • (С2) Система 2 — сервер глобальной авторизации. В ней пользователь уже авторизован, скорее всего, через cookie.
шаг кто что делает кому
(П) → (переходит по ссылке) → C1 с любыми параметрами.

C1 хочет перенять авторизацию у С2.
C1 генерирует случайный ID (ID) и ключ (KEY). Никаких ограничений на эти значения не накладывается, кроме того, что они должны быть достаточно стойки к подбору и, желательно, состоять из печатных символов. Например, за каждый из них можно взять 16 случайных байт, взятых из /dev/urandom в UNIX-системах и GetRandom() в Windows.

1 C1 → (делает GET-запрос напрямую) → С2 с параметрами ga_id=ID&ga_key=KEY

С2 запоминает соответствие ID и KEY.

2 С1 → (перенаправление браузера пользователя) → С2 с параметрами ga_id=ID&ga_url=URL&ga_check=CHECK
  • URL — URL для возврата на С1, на который С2 будет передавать данные и на который же С2 будет отправлять пользователя редиректом обратно.
  • Если CHECK=0 или не передаётся, и пользователь не авторизован в С2, она должна потребовать от него авторизоваться.

С2 даётся возможность прочитать cookie пользователя и получить данные о нём.

3 С2 → (делает POST-запрос напрямую) → С1 с параметрами ga_client=1&ga_id=ID&ga_key=KEY&ga_data=DATA&ga_nologin=NOLOGIN
  • DATA — данные о вошедшем пользователе в произвольном формате, кодированные в JSON.
  • NOLOGIN=1 и DATA="" (пустой строке), если и только если CHECK=1 и пользователь не авторизован в С2.
  • Иначе NOLOGIN не передаётся или NOLOGIN=0.

С1 запоминает соответствие ID и переданных данных.

4 С2 → (перенаправление браузера пользователя) → С1 с параметрами ga_client=1&ga_id=ID&ga_res=CODE
  • CODE — HTTP-код статуса, полученный от POST-запроса из предыдущего пункта.

С1 может взять сохранённые в предыдущем пункте данные и на их основе авторизовать пользователя.

И ID и ключ являются «секретными», но ID знают и сервера, и пользователь (ID передаётся в браузер), а ключ — только сами сервера. За счёт этого достигается безопасность: пользователь не может сам передать произвольные данные авторизации на сервер, не зная ключа. Для дополнительной защиты всё это можно просто пустить через HTTPS (SSL). Понятно, что кэш, в котором сохраняются соответствия ID и ключа, не должен быть доступен для чтения внешнему пользователю, иначе вся защита накрывается медным тазом. Для усиления защиты опять-таки можно дополнительно создать список доверенных серверов и при приёме данных авторизации проверять IP.

Как уже было сказано выше, данные авторизации — произвольные в JSON-формате. Однако, удобно специфицировать его чуть точнее: хеш, в котором есть поля user_name (логин), user_email (адрес электронной почты) и необязательные поля user_url (URL «домашней страницы» пользователя) и user_real_name («настоящее имя» пользователя).

OpenID, на самом деле, работает похоже, но почему-то адски глючит и его страшно использовать на своих внутренних ресурсах. :)

Что ещё нужно (TODO): обработка и передача описаний ошибок между серверами для их показа пользователю в удобном виде, чтобы не получалось, как в OpenID — «произошла ошибка» («она утонула» ©).

Плюс FoF

(Тем кто читает это в вебе, можно в принципе и не читать)

Теперь пусть есть некая система типа FoF, в которой действует данный метод глобальной авторизации, и которая должна авторизоваться на доверенных серверах, на которых эта глобальная авторизация тоже действует. Естественно, пароль пользователя хранить в открытом виде в базе при этом нельзя, да его в данных глобальной авторизации и нету. Пример такой ситуации — это FoF (фидридер) — он должен забирать RSS-ленты пользователя, в которых пользователь тоже авторизуется.

  • FoF видит в урле рсс’ки &fof_sudo=1, генерирует случайный ID и добавляет в запрос
    • Cookie: fof_sudo_id=ID
  • Генератор рсс видит cookie fof_sudo_id и делает запрос к fof: /fof-sudo.php?id=ID
  • FoF отвечает {'user_name':'user@custis.ru'} и забывает ID
  • Генератор рсс принимает тот факт, что теперь он работает под юзером user@custis.ru и отдаёт правильную рсс’ку