Статьи

Пишем себе немного OpenID-авторизации

в рубрике web-разработка, Другое

3065ad5259bd

Взгляд в будущее

В последнее время всякие социальные сети и вообще сервисы-лидеры интернета по посещаемости и количеству аккаунтов завели очень неплохую, на мой взгляд, привычку — предоставление уникальных 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.

if openid == ‘google’:

    openid = ‘https://www.google.com/accounts/o8/id’

 

try:

    authrequest = self.consumer.begin(openid) # панеслася

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 и организовать интересующимся немного кода по кросслогину из твиттера.

VN:F [1.9.5_1105]
Rating: +1 (from 1 vote)

Комментариев: 3 на “Пишем себе немного OpenID-авторизации”

  1. :

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

  2. Так и не понял, что имелось в виду в Вашем комментарии.

    VN:F [1.9.5_1105]
    Rating: +3 (from 3 votes)
  3. Это кто-то процитировал Вас на другом сайте. WordPress иногда почему-то ссылки такие добавляет комментарием к статье.

    VN:F [1.9.5_1105]
    Rating: +1 (from 1 vote)

Прокомментировать

Вы должны быть авторизованы для комментирования.

Партнеры

Microsoft ITONLINE Group ScrimTrek IT Trainings

© Careerlab, ITONLINE GROUP 2012 Команда Software People

+7 (495) 933-01-33

team@softwarepeople.ru