Изменения

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

7599 байтов добавлено, 15:50, 1 марта 2017
Нет описания правки
{{Box|{{Warning}} Ниже описывается Велосипедная Реализация SSO. В современном мире доступен OAuth2, про него можно почитать тут: [[Кратко об SSO через OAuth2]]}}
 
…или как реализовать простой [[rupedia:Технология единого входа|Single Sign-on]] в веб-системах.
* (П) Пользователь.
* ({{/С1}}) Система 1 — клиент глобальной авторизации. В неё пришёл пользователь без авторизации.* ({{/С2}}) Система 2 — сервер глобальной авторизации. В ней пользователь уже авторизован, скорее всего, через cookie.
{| class="simpletable"
! кто
! что делает
! width=35%| кому!width=45%| возможные ошибки
|-
|
|valign=top| (П) →
|valign=top| (переходит по ссылке) →
|valign=top| <tt style="background-color: #e0ffe0">C1<{{/tt> С1}} с любыми параметрами.<br />&rArr; <tt style="background-color: #e0ffe0">C1<{{/tt> С1}} хочет перенять авторизацию у <tt style="background-color: #e0e0ff">{{/С2</tt>}}. <br /><tt style="background-color: #e0ffe0">C1<{{/tt> С1}} генерирует случайный ID (ID) и ключ (KEY). Никаких ограничений на эти значения не накладывается, кроме того, что они должны быть достаточно стойки к подбору и, желательно, состоять из печатных символов. Например, за каждый из них можно взять 16 случайных байт, взятых из <tt>/dev/urandom</tt> в UNIX-системах и <tt>GetRandom()</tt> в Windows.|valign=top|
|-
|valign=top align=center| 1
|valign=top| <tt style="background-color: #e0ffe0">C1<{{/tt>С1}}&nbsp;&rarr;
|valign=top| (делает GET-запрос напрямую)&nbsp;&rarr;
|valign=top| <tt style="background-color: #e0e0ff">С2<{{/tt> С2}} с параметрами <tt style="background-color: #ffe0e0">{{/p}}ga_id=ID&ga_key=KEY</tt> <br />&rArr; <tt style{{/С2}} запоминает соответствие ID и KEY.|valign="backgroundtop|{{/С2}} не ответила или ответ не в формате JSON («по факту», а не по MIME-colorтипу ответа): #e0e0ff">: '''Сервер авторизации XXX недоступен со стороны системы {{/С1}}; код HTTP: XXX; MIME-тип ответа: XXX.'''{{/С2}} ответила хешем в формате JSON, в котором присутствует поле «<tt>error</tt> запоминает соответствие ID и KEY» с непустым значением EEE:: '''Сервер авторизации XXX сообщает об ошибке начала сессии авторизации: EEE.'''
|-
|valign=top align=center| 2
|valign=top| <tt style="background-color: #e0ffe0">С1<{{/tt>С1}}&nbsp;&rarr;
|valign=top| (перенаправление браузера пользователя)&nbsp;&rarr;
|valign=top| <tt style="background-color: #e0e0ff">{{/С2</tt> }} с параметрами <tt style="background-color: #ffe0e0">{{/p}}ga_id=ID&ga_url=URL&ga_check=CHECK</tt>и опционально параметром {{/p}}ga_message=TEXT</tt>.
* URL — URL для возврата на <tt style="background-color: #e0ffe0">С1</tt>, на который <tt style="background-color: #e0e0ff">{{/С2</tt> }} будет передавать данные и на который же <tt style="background-color: #e0e0ff">{{/С2</tt> }} будет отправлять пользователя редиректом обратно. Если URL не передаётся, за него принимается HTTP-заголовок Referer.* Если CHECK=0 или не передаётся, и пользователь не авторизован в <tt style="background-color: #e0e0ff">{{/С2</tt>}}, она должна потребовать от него авторизоваться.* TEXT — название {{/С1}} или любое сообщение, которое нужно показать пользователю в случае запроса авторизации.
&rArr; <tt style="background-color: #e0e0ff">С2<{{/tt> С2}} даётся возможность прочитать cookie пользователя и получить данные о нём.Если это необходимо, на этом же шаге {{/С2}} может запросить у пользователь подтверждение передачи его учётных данных внешней системе, и в случае отрицательного решения передать на следующем шаге NOLOGIN.|valign=top|URL, на который происходит перенаправление, недоступен пользователю.: ''Увы, в данном случае никакого сообщения об ошибке выдать не получится :(. Некому — с {{/С1}} уже ушли, а на {{/С2}} ещё не пришли.'' В запросе не передан или передан некорректный параметр ga_url, и также отсутствует HTTP-заголовок Referer, по крайней мере такой, который можно распарсить.: '''В запросе авторизации от клиентской системы отсутствует URL (''или неверный: URL'') обратной связи.'''
|-
|valign=top align=center| 3
|valign=top| <tt style="background-color: #e0e0ff">С2<{{/tt>С2}}&nbsp;&rarr;
|valign=top| (делает POST-запрос напрямую)&nbsp;&rarr;
|valign=top| <tt style="background-color: #e0ffe0">С1<{{/tt> С1}} с параметрами <tt style="background-color: #ffe0e0">{{/p}}ga_client=1&ga_id=ID&ga_key=KEY&ga_data=DATA&ga_nologin=NOLOGIN</tt>
* DATA — данные о вошедшем пользователе в произвольном формате, кодированные в [[rupedia:JSON|JSON]].
* NOLOGIN=1 и DATA="" (пустой строке), если и только если CHECK=1 и пользователь не авторизован в <tt style="background-color: #e0e0ff">С2<{{/tt>С2}}.
* Иначе NOLOGIN не передаётся или NOLOGIN=0.
&rArr; <tt style="background-color: #e0ffe0">С1</tt> запоминает соответствие ID и переданных данных.
|valign=top|
 
ID неизвестен серверу.
: '''Попытка авторизоваться по неверному или устаревшему идентификатору сессии, попробуйте ещё раз ''(ссылка — URL к {{/С1}})''.'''
 
CHECK=0 и авторизовать пользователя в {{/С2}} невозможно.
: '''Сайт XXX ''(описание: TEXT)'' требует авторизации, а вы не авторизованы в {{/С2}}.''' (TEXT из предыдущего шага)
: Логично, если данное сообщение показывает пользователю {{/С2}}.
 
Пользователь не доверяет {{/С1}} и запретил {{/С2}} передавать ей свои авторизационные данные.
: Логично, если {{/С2}} покажет пользователю ошибку и/или страницу настройки доверия к {{/С1}}.
 
{{/С1}} не ответила или ответ не в формате JSON. MIME-тип, опять-таки, не проверяется.
: '''Сайт XXX ''(описание: TEXT)'' недоступен с сервера авторизации. Код HTTP: XXX; MIME-тип ответа: XXX.'''
 
{{/С1}} ответила хешем в формате JSON, в котором присутствует поле «<tt>error</tt>» с непустым значением EEE:
: '''Сайт XXX ''(описание: TEXT)'' сообщает об ошибке авторизации: EEE.'''
: Ошибками EEE могут быть, например:
:* ID неизвестен клиенту: <br /> '''Попытка авторизоваться по неверному или устаревшему идентификатору сессии, попробуйте ещё раз ''(ссылка)''.'''
:* Известному на клиенте ID соответствует неверный ключ KEY: <br /> '''Секретный ключ неверен, проверка подлинности сервера авторизации XXX не удалась. Возможно, вас дурят.'''
:* Не вышло декодировать DATA или считать NOLOGIN: <br /> '''Формат данных, переданных с сервера авторизации, неизвестен.''' ''Опционально — показать также и сами данные.''
:* Не удалось сохранить данные в хранилище состояний клиента (базе данных, кэше и т. п.): <br /> '''Внутренняя ошибка клиента — хранилище состояний недоступно.'''
|-
|valign=top align=center| 4
|valign=top| <tt style="background-color: #e0e0ff">С2<{{/tt>С2}}&nbsp;&rarr;
|valign=top| (перенаправление браузера пользователя)&nbsp;&rarr;
|valign=top| <tt style="background-color: #e0ffe0">С1<{{/tt> С1}} с параметрами <tt style="background-color: #ffe0e0">{{/p}}ga_client=1&ga_id=ID&ga_res=CODE</tt>
* CODE — HTTP-код статуса, полученный от POST-запроса из предыдущего пункта.
&rArr; <tt style="background-color: #e0ffe0">С1<{{/tt> С1}} может взять сохранённые в предыдущем пункте данные и на их основе авторизовать пользователя.|valign=top|ID неизвестен клиенту:: '''Попытка авторизации по неверному или устаревшему идентификатору сессии, либо сервер авторизации не смог передать данные учётной записи. Попробуйте ещё раз ''(ссылка)''.''' <span style="color:red">Самая противная ошибка</span> — это ''Redirect-Loop'' — бесконечное перенаправление, которое можно спровоцировать, если при ошибке авторизации никак не сохранить факт ошибки в браузере пользователя (например, с помощью cookie) и вместо того, чтобы показать страницу с ошибкой, отправить пользователя обратно на шаг 2 (через шаг 1).
|}
 
Важный момент: после успешной обработки любого запроса глобальной авторизации (запроса с параметрами ga_id или ga_client и т. п.), пришедшего со стороны браузера пользователя, как клиентской, так и серверной стороной, должно осуществляться перенаправление (<tt>HTTP 302 Moved temporarily</tt>) на адрес, не содержащий в себе параметров ga_id и т. п., для исключения повторной отправки запроса пользователем при нажатии «Обновить страницу».
И ID и ключ являются «секретными», но ID знают и сервера, и пользователь (ID передаётся в браузер), а ключ — только сами сервера. За счёт этого достигается безопасность: пользователь не может сам передать произвольные данные авторизации на сервер, не зная ключа. Для дополнительной защиты всё это можно просто пустить через HTTPS (SSL). Понятно, что кэш, в котором сохраняются соответствия ID и ключа, не должен быть доступен для чтения внешнему пользователю, иначе вся защита накрывается медным тазом. Для усиления защиты опять-таки можно дополнительно создать список доверенных серверов и при приёме данных авторизации проверять IP.
OpenID, на самом деле, работает похоже, но ''почему-то'' адски глючит и его страшно использовать на своих внутренних ресурсах. :)
Что ещё нужно (TODO): В реализации протокола очень желательна обработка ошибок и передача описаний ошибок между серверами для их показа показ пользователю в удобном виде, чтобы не получалось, как в OpenID — «произошла ошибка» («она утонула» ©(то есть «произошла ошибка» без каких-либо деталей).
== Плюс FoF FoF_Sudo ==
(Тем кто читает это в вебе, можно в принципе и не читать)FoF_Sudo — беспарольная авторизация типа «от системы к системе».
Теперь Идея такая: пусть есть некая система типа (например [[lib:FeedOnFeeds|FoF]] — фидридер), в которой действует данный метод глобальной авторизацииглобальная авторизация (в качестве сервера выступает другая система), и которая пусть эта система должна авторизоваться на доверенных серверах, на которых эта глобальная авторизация тоже действует(в примере — FoF должен забирать защищённые RSS-ленты от имени разных пользователей). Естественно, Но при этом (!) пароль пользователя хранить в открытом виде в базе FoF при этом нельзя. Да если бы и было можно, да его в данных глобальной авторизации и его просто нету. Пример При этом нужно, чтобы при передаче такой ситуации — это FoF (фидридер) — он должен забирать RSSвот беспарольной авторизации какой-ленты пользователя, в которых то совершенно левый пользователь тоже авторизуетсяне смог сделать так же и зайти под произвольным пользователем.
Как это сделать? Ответ — одноразовые ключи на каждый запрос: * FoF видит в урле рсс’ки &fof_sudo=1(что_угодно). Что_угодно — совершенно что угодно, единственно, в случае FoF оно должно быть разное для разных пользователей, иначе FoF двух пользователей подпишет на один и тот же защищённый фид, а обновлять будет вообще как попало — то от имени одного, то от имени другого пользователя.* FoF генерирует случайный ID , запоминает соответствие этому ID пользователя и добавляет в запроскукис:
** Cookie: fof_sudo_id=ID
* Генератор рсс Система, к которой произошло обращение, видит cookie этот кукис (fof_sudo_id ) и делает обратный запрос к fof: /fof-sudo.php?id=ID* FoF отвечает данными пользователя, которые он помнит по этому ID, в формате JSON ({'user_name':'user@custis.ru'} ) и забывает ID* Генератор рсс принимает тот фактВнешняя система верит, что теперь он работает надо авторизоваться под юзером именем юзера user@custis.ru (и , например, отдаёт правильную рсс’куRSS’ку) {{Box|{{Warning}} И это тоже покрывается OAuth2.}}
[[Категория:Разработка]]