Пишем себе немного OpenID-авторизации
в рубрике web-разработка, Другое

Взгляд в будущее
В последнее время всякие социальные сети и вообще сервисы-лидеры интернета по посещаемости и количеству аккаунтов завели очень неплохую, на мой взгляд, привычку — предоставление уникальных OpenID-идентификаторов для пользователей, дабы с их использованием можно было зайти на сторонний сайт. Кроме того, параллельно развивается очень похожая, но все-таки не совсем производная технология OAuth, которая появилась на свет благодаря стараниям создателей небезызвестного Twitter и, цитируя википедию, «позволяет предоставить третьей стороне доступ к защищенным ресурсам пользователя, без необходимости передавать ей (третьей стороне) логин и пароль».
Лично меня такая тенденция очень радует и, более того, я почти уверен, что за подобной технологией будущее. В частности, в будущем обязательно появятся новые мэшапы для агрегирования информации с кучи сайтов (в частности, хочется вспомнить очень хороший, но несправедливо забытый сервис Yahoo Pipes, который так и не смог покорить сердца и умы просто потому, что его время тогда еще не пришло. Возможно, все еще впереди), а именно такой «форм-фактор» требует логина на кучу сервисов сразу.
Петь дифирамбы подобным технологиям можно очень долго, но лично меня, например, всегда напрягали сайты, на которых надо с нуля регистрироваться, чтобы что-нибудь скачать. Ведь все мы неизменно сталкивались с тем, что когда ищешь, где скачать тот или иной материал — он зачастую оказывается на каком-то совершенно левом и непонятном сайте с названием в духе allbooksmusicwarezzz.omg.su, который ко всему прочему еще и регистрацию требует. Да нет, дело не в пиратстве, дело в том, что сайтов со всяким барахлом, сделанных на коленке, уйма. А вот человеческая память на логины-пароли ограничена, и тут уж ничего не сделаешь. Но приятный момент здесь еще и в том, что многие OpenID-провайдеры кроме информации, непосредственно служащей для авторизации, могут по запросу предоставить еще и базовую информацию о пользователе — e-mail, полное имя, предпочтительный язык и т.п. Причем на многих подобных сервисах можно управлять тем, что отдавать, а что сохранить в секрете. Например, разве пользователю не будет приятно, когда он, зайдя на очередной сайт, увидит приветливую надпись «Добро пожаловать, Вася!» на чистом русском языке, да еще и профиль уже готов к употреблению, вместе с аватаром, привычками и кличкой нежно любимого кота?
Делаем дело и работаем работу
Довольно лирики, думаю, кому оно надо как разработчику — он и так все вышенаписанное уже знает, а простым пользователям дальнейший материал вряд ли будет интересен. Еще больше хвалебных речей и рассуждений легко найти в блоге Ивана Сагалаева, а мы давайте попробуем сделать свою систему авторизации через OpenID (например, для блога) на Python, с преферансом и пианистками.
Для своего блога, который сейчас находится в разработке у меня в папочке Projects, я решил вообще отказаться от системы регистрации и авторизации через логин-пароль, а оставить только OpenID. В качестве фреймворка был выбран Pylons, а для прикручивания OpenID к Django-проектам существует и развивается проект с простым и понятным названием django-openid. Для Pylons, в общем-то, тоже существует решение под названием AuthKit, однако с ним у меня отношения как-то не очень сложились, а все, что я нашел в сети — это несколько сниппетов, в которых и пришлось разбираться.
Для начала надо установить модуль python-openid, чтобы обеспечить поддержку технологии, а потом создаем контроллер (обработчик запроса по URL, ближайшая ассоциация — джанговский views.py) и начинаем колдовать.
$ paster controller auth
Сразу оговорюсь, что код рабочий ровно до той степени, которая обеспечивает непосредственно аутентификацию, что делать дальше и как это все оформлять — решать только вам, господа творцы. Начало довольно стандартное:
from openid.consumer.consumer import Consumer, SUCCESS, FAILURE, DiscoveryFailure
from openid.store import filestore
from openid import sreg
from datetime import datetime
from hashlib import md5
class AuthController(BaseController):
def __before__(self):
self.openid_session = session.get(“openid_session”, {}) # проверяем, не существует ли openid-сессии
def index(self):
return render(‘/accounts/enter.html’)
@rest.dispatch_on(POST=“signin_POST”) # разделяем GET- и POST-запросы по разным обработчикам для удобства
def signin(self):
if c.user: # проверяем, не попытался ли уже залогиненый юзер зайти еще раз
session['message'] = ‘Already signed in.’
session.save()
redirect(url(action=‘index’)) # и если да, то не пущаем
session.clear()
return render(‘/index.html’)
Теперь подходим к самому интересному:
def signin_POST(self):
problem_msg = ‘A problem ocurred comunicating to your OpenID server. Please try again.’
g.openid_store = filestore.FileOpenIDStore(‘.’) # создаем временное хранилище для хранения OpenID-данных, g здесь-массив глобальных переменных Pylons
self.consumer = Consumer(self.openid_session, g.openid_store) # ага, вот и наш клиент
openid = request.params.get(‘openid’, None) # достаем из запроса строку с OpenID - идентификатором
Ага, а вот тут немного магии. SReg — это то самое расширение, которое позволяет нам запросить у сервера дополнительные данные о пользователе. Поля, значение которых хотелось бы узнать, перечисляем в списке optional, а дополнительные данные, если что, всегда можно запросить у пользователя потом. Если же какая-то дополнительная информация требуется прямо кровь из носу, то можно запросить ее в required, но если сервер ее не отдаст — будет ошибка.
...
sreg_request = sreg.SRegRequest(
#required=['email'],
optional=['fullname', 'timezone', 'language', 'email', 'nickname']
)
if openid is None:
session['message'] = problem_msg
session.save()
return render(‘/index.html’)
...
Здесь я позволил себе схалявить и написал этот код только для того, чтобы объяснить разницу между простым OpenID и кросс-логином с гугловского аккаунта. Дело в том, что гугл не представляет пользователям OpenID-идентификатора вида https://vasya_pupkin.google.com/, а все куда проще и веселее. URL идентификации у всех пользователей гугла выглядит абсолютно одинаково — https://www.google.com/accounts/o8/id. Любопытно то, что при запросе на этот URL гугл отдает готовый XRDS (XML-подобного вида документ, возвращаемый сервером по стандарту OpenID 2.0), который уже содержит все необходимое для авторизации, а вам как пользователю присваивается уникальный ID, который и является, по сути, идентификатором OpenID.
openid = ‘https://www.google.com/accounts/o8/id’ try: authrequest = self.consumer.begin(openid) # панесласяif openid == ‘google’:
except DiscoveryFailure, e: # а вдруг ошибка в адресе или такой провайдер существует только в твоем воображении?
session['message'] = problem_msg
session.save()
return redirect(url(controller=‘auth’, action=’signin’))
authrequest.addExtension(sreg_request) # подключаем SReg, дабы извлечь требуемые поля для профиля
redirecturl = authrequest.redirectURL(h.url_for(‘/’, qualified=True),
return_to=h.url_for(action=‘verified’, qualified=True),
immediate=False
) # после всего, что у нас было с сервером, надо как-то жить дальше
session['openid_session'] = self.openid_session
session.save()
return redirect(url(redirecturl))
Ну, теперь можно поговорить с сервером, останемся ли мы друзьями.
...
def verified(self):
problem_msg = ‘A problem ocurred comunicating to your OpenID server. Please try again.’
self.consumer = Consumer(self.openid_session, g.openid_store)
info = self.consumer.complete(request.params, (h.url_for(controller=‘auth’,
action=‘verified’,
qualified=True)))
if info.status == SUCCESS: # все пучком
sreg_response = sreg.SRegResponse.fromSuccessResponse(info) # извлекаем затребованные в SReg поля
user = User(by_openid=info.identity_url) # ищем юзера по идентификатору в базе
if not user.exist: # а вот тут можно делать что угодно. Например, внести юзера в базу
newuser = User()
try:
email = sreg_response.get(‘email’, u”),
except:
email = u”
newuser.create(
openid = unicode(info.identity_url),
email = email,
password = unicode(md5(info.identity_url).hexdigest()),
ip = request.environ['REMOTE_ADDR']
Вот, собственно, и все. Что делать с полученными данными — засовывать в куки, продолжать регистрацию и просить у пользователя дополнительную информацию — решать только вам. Да, и еще, данный код не работает с OpenID от Yahoo. Если охота по завещанию Козьмы Пруткова позрить в корень — есть информация все в том же блоге Ивана Сагалаева. Буду рад услышать любую критику, уточнения, предложения. Постараюсь в дальнейшем разобраться с OAuth и организовать интересующимся немного кода по кросслогину из твиттера.
Комментариев: 3 на “Пишем себе немного OpenID-авторизации”
Прокомментировать
Вы должны быть авторизованы для комментирования.




:
[...] Николай пишет: В последнее время всякие социальные сети и вообще сервисы-лидеры интернета по посещаемости и количеству аккаунтов завели очень неплохую, на мой взгляд, привычку — предоставление уникальных OpenID-идентификаторов для пользователей, …. if info.status == SUCCESS: # все пучком sreg_response = sreg.SRegResponse.fromSuccessResponse(info) # извлекаем затребованные в SReg поля. user = User(by_openid=info.identity_url) # ищем юзера по идентификатору в базе … [...]
27 августа, 2010 в 17:01
Марков Николай:
Так и не понял, что имелось в виду в Вашем комментарии.
27 августа, 2010 в 17:07
Арсеньева Елена:
Это кто-то процитировал Вас на другом сайте. WordPress иногда почему-то ссылки такие добавляет комментарием к статье.
31 августа, 2010 в 12:55