За несколько лет своего существования протокол OAuth смог зарекомендовать себя и обрел широкую популярность. Не удивительно, ведь это удобный и безопасный способ обмена данными между сервисами. Если до него для того, чтобы получить данные пользователя со стороннего сервиса, нужно было запрашивать пользовательские логин и пароль, что, разумеется, небезопасно, то с помощью OAuth всё происходит иначе.
Для начала немного терминов. В OAuth авторизации выделяются три роли – клиент, сервер и владелец ресурса (пользователь). Клиент – это сервис, которому предоставляется доступ к данным, сервер – это сервис, который хранит данные, а владелец ресурса – это пользователь, который предоставляет свои данные. Или на примере: Вы сделали на своем сайте авторизацию через ВКонтакте. На этот сайт зашел пользователь и хочет авторизоваться. В данном случае клиентом является Ваш сайт, сервером – ВКонтакте, а владельцем ресурса – этот самый пользователь.
Еще один термин, с которым придется часто сталкиваться – это токен. Токен в нашем случае – это средство авторизации в OAuth запросах, текстовый ключ, который предоставляет ограниченный временный доступ к данным владельца ресурса на сервере. Нередко используется связка из нескольких токенов.
Способы OAuth авторизации на разных серверах отличаются, но схема у них одна:
- Клиент отправляет на сервер запрос на получение токена авторизации. В запросе используются идентификационные данные клиента.
- После распознавания клиента сервер отдает токен авторизации.
- Клиент, используя этот токен, перенаправляет пользователя на сервер
- На сервере пользователь авторизовывается и подтверждает доступ клиенту к своим данным.
- После авторизации и подтверждения пользователь перенаправляется к клиенту, передавая при этом дополнительные токены (обычно один или два) доступа (обычно в GET-параметрах)
- Используя токен доступа клиент обращается к серверу за данными и получает их уже без подтверждения пользователем. Обычно доступ к данным по одному и тому же токену ограничивается лишь по времени, некоторые серверы дополнительно ограничивают количество запросов.
Шаги 1 и 2 – редкость, обычно идентификационные данные клиента передаются серверу вместе с перенаправлением пользователя на авторизацию вместо токена авторизации. Идентификационные данные клиенту выдаются при регистрации сайта на сервере, зачастую в виде приложения.
ВКонтакте – это социальная сеть с огромной аудиторией – более 40 млн. посетителей ежеджевно. Почему бы не упростить регистрацию этих людей на Вашем сайте? Для вступления достаточно, пора приступить к делу.
Что случилось?
Здравствуй, дорогой читатель. Если тебе хотя бы однажды доводилось работать с API Вконтакте и при этом писать все на python, вероятно, авторизация приложения заставила тебя сделать несколько приседаний, после которых ног либо не чувствуешь и падаешь в обморок, либо вкачиваешь квадрицепс и все же пробиваешь API, как Ван Дамм.
По какой-то причине этот, казалось бы, самый непримечательный этап поначалу отнимает огромное количество сил и времени. Моя задача: помочь читателям Хабра избежать травм ног.
Далее я предлагаю рассмотреть небольшую библиотеку, позволяющую в одну строчку авторизовать свое приложение для конкретного пользователя и получить access_token. В конце статьи представлена ссылка на github-репозиторий этой библиотеки с quickstart’ом в README-файле.
Регистрация приложения
Скорее всего Вы уже зарегистрированы в соц. сети ВКонтакте, если нет, то Вам придется это сделать. Указывать номер телефона при регистрации обязательно. Переживать не стоит — никакой смс-рассылки от этих сервисов не приходит. Далее необходимо создать приложение. (Скриншоты были сделаны в апреле 2015, с той поры интерфейс мог измениться)
После смс-подтверждения создания приложения оно, как можно догадаться, создаётся и Вы попадаете на страницу её редактирования. Всю информацию и иконки желателно добавить, но больше всего нас там интересуют id приложения и защищённый ключ— это и есть идентификационные данные нашего сайта:
Приложения ВКонтакте поддерживают работу с несколькими доменами, так что можно создать одно приложение сразу для всех Ваших сайтов.
Задача
Хотим небольшой модуль, который позволяет провести авторизацию красиво, универсально и максимально надежно, а использовать который очень просто. Стоит сказать, что данное решение является усовершенствованием и обобщением варианта, предложенного в этой статье.
Итак, используем python3.5, библиотеку для html запросов requests и getpass для скрытого ввода пароля.
Наша задача: несколько раз обратиться по верному адресу, каждый раз парсить
, отправлять ответ и наконец получить желанный access_token.
1. Авторизуйтесь на Facebook и перейдите по ссылке.
Нажмите на кнопку «Add a New App», в появившейся форме укажите название приложения и ваш email для связи:
Обратите внимание: перевод этого раздела на русский язык на момент написания этой статьи хоть и является читабельным, но не до конца корректен. Возможно, вы захотите работать с Facebook на английском языке, для этого просто смените язык в настройках своего аккаунта.
2. После проверки безопасности откроется страница настроек приложения, найдите на ней раздел «Вход через Facebook» и нажмите «Настроить»:
3. Укажите «Веб» в качестве платформы:
4. В открывшемся окне укажите адрес своего магазина, нажмите кнопку «Save» и «Продолжить»:
5. Перейдите в основные настройки приложения на боковой панели, добавьте домен сайта и URL-адрес политики конфиденциальности:
6. Введите URL магазина:
7. В этом же разделе отображается идентификатор приложения и токен доступа («секрет приложения)»:
8. Внесите эту информацию в бэк-офисе вашего магазина:
9. В настройках приложения «Вход через Facebook» необходимо заполнить «Действительные URI перенаправления для OAuth»:
Здесь необходимо вставить адрес: https://домен/auth/facebook/callback, где слово «домен» необходимо заменить доменом вашего магазина, например myshop.ru.
Важно: если ваш домен включает «www», то данный адрес тоже должен содержать «www».
10. В верхней панели располагается переключатель и текст «Статус: в разработке». Необходимо его активировать, чтобы статус сменился на «Опубликовано»:
Все готово к работе. Вы также можете добавить описание и логотип своего приложения.
Реализация
Начнем с создания класса. При инициализации будем требовать список «разрешений», к которым приложение хочет получить доступ, id этого приложения и версию API VK. Плюсом добавим несколько необязательных параметров, значение каждого из которых прояснится далее.
Метод __init__
class VKAuth(object): def __init__(self, permissions, app_id, api_v, email=None, pswd=None, two_factor_auth=False, security_code=None, auto_access=True): «»» Args: permissions: list of Strings with permissions to get from API app_id: (String) vk app id that one can get from vk.com api_v: (String) vk API version «»» self.session = requests.Session() self.form_parser = FormParser() self.user_id = None self.access_token = None self.response = None self.permissions = permissions self.api_v = api_v self.app_id = app_id self.two_factor_auth= two_factor_auth self.security_code = security_code self.email = email self.pswd = pswd self.auto_access = auto_access if security_code != None and two_factor_auth == False: raise RuntimeError(‘Security code provided for non-two-factor authorization’)
Как было сказано в уже упомянутой статье, нам необходимо искусно ворочать cookie и redirect’ы. Все это за нас делает библиотека requests с объектом класса Session. Заведем и себе такой в поле self.session. Для парсинга html документа используется стандартный класс HTMLParser из модуля html.parser. Для парсера тоже написан класс (FormParser), разбирать который большого смысла нет, так как он почти полностью повторяет таковой из упомянутой статьи. Существенное отличие лишь в том, что использованный здесь позволяет изящно отклонить авторизацию приложения на последнем шаге, если вы вдруг передумали.
Поля user_id и access_token будут заполнены после успешной авторизации, response хранит в себе результат последнего html запроса.
Пользователю библиотеки предоставим один-единственный метод – authorize, который совершает 3 шага:
- запрос на авторизацию приложения
- авторизация пользователя 2.1 введение кода-ключа в случае двух-факторной авторизации
- подтверждение разрешения на использование permissions
Пройдемся по каждому шагу.
Шаг 1. Запрос на авторизацию приложения
Аккуратно составляем url запроса (про параметры можно прочитать здесь), отправляем запрос и парсим полученный html.
Метод authorize для Шага 1
def authorize(self): api_auth_url = ‘https://oauth.vk.com/authorize’ app_id = self.app_id permissions = self.permissions redirect_uri = ‘https://oauth.vk.com/blank.html’ display = ‘wap’ api_version = self.api_v auth_url_template = ‘{0}?client_id={1}&scope={2}&redirect_uri={3}&display={4}&v={5}&response_type=token’ auth_url = auth_url_template.format(api_auth_url, app_id, ‘,’.join(permissions), redirect_uri, display, api_version) self.response = self.session.get(auth_url) # look for element in response html and parse it if not self._parse_form(): raise RuntimeError(‘No element found. Please, check url address’)
Шаг 2. Авторизация пользователя
Реализованы методы _log_in() и _two_fact_auth() для [не]успешной авторизации пользователя в вк, если он не авторизован (а он точно не авторизован). Оба метода используют ранее определенные поля email, pswd, two_factor_auth и security_code. Если какое-то из полей не было подано аргументом при инициализации объекта класса VKAuth, их попросят ввести в консоли, а случае неудачи попросят ввести заново. Двух-факторная авторизация опциональна и по умолчанию отключена, и наш модуль уведомляет пользователя о ее присутствии ошибкой.
Метод authorize для Шага 2 (продолжение Шага 1)
#look for element in response html and parse it if not self._parse_form(): raise RuntimeError(‘No element found. Please, check url address’) else: # try to log in with email and password (stored or expected to be entered) while not self._log_in(): pass; # handling two-factor authentication # expecting a security code to enter here if self.two_factor_auth: self._two_fact_auth()
Метод _log_in для Шага 2
def _log_in(self): if self.email == None: self.email = » while self.email.strip() == »: self.email = input(‘Enter an email to log in: ‘) if self.pswd == None: self.pswd = » while self.pswd.strip() == »: self.pswd = getpass.getpass(‘Enter the password: ‘) self._submit_form({’email’: self.email, ‘pass’: self.pswd}) if not self._parse_form(): raise RuntimeError(‘No element found. Please, check url address’) # if wrong email or password if ‘pass’ in self.form_parser.params: print(‘Wrong email or password’) self.email = None self.pswd = None return False elif ‘code’ in self.form_parser.params and not self.two_factor_auth: raise RuntimeError(‘Two-factor authentication expected from VK.\nChange `two_factor_auth` to `True` and provide a security code.’) else: return True
Метод _two_fact_auth для Шага 2
def _two_fact_auth(self): prefix = ‘https://m.vk.com’ if prefix not in self.form_parser.url: self.form_parser.url = prefix + self.form_parser.url if self.security_code == None: self.security_code = input(‘Enter security code for two-factor authentication: ‘) self._submit_form({‘code’: self.security_code}) if not self._parse_form(): raise RuntimeError(‘No element found. Please, check url address’)
Простая авторизация
На этом этапе – всё довольно рационально. После того как вы подключите класс к файлу, останется получить экземпляр VKAuth, передав ему настройки. Ниже описываем простой обработчик, который отлавливает переменную $_GET[code] и проверяем аутентификацию.
require_once(«VKAuth.php»); $vk = new VKAuth(array( «client_id» => «ID_приложения», «client_secret» => «защищенный_ключ», «redirect_uri» => «адрес_сайта» )); if(isset($_GET[«code»])){ if($vk->auth($_GET[«code»])){ // Делаем свои дела } }
Далее, чтобы произвести авторизацию, вам нужно будет проверить наличие пользователя у себя в базе данных и, если его нет, то добавить. В противном случае обновить его данные (перед обновлением желательно проверить — изменились ли они). Касательно базы данных обычно добавляют два поля: тип авторизации и id пользователя в социальной сети. Вот так осуществляется аутентификация и авторизация через социальную сеть «ВКонтакте».
В архиве вы найдёте готовый пример работы с классом VKAuth, где выводятся данные пользователя.
Функция «запомнить меня»
Именно благодаря механизму срока годности печенек и работает функция «запомнить меня». По умолчанию, сервис создает для пользователя сессию, истекающую в момент закрытия браузера. Но если пользователь устанавливает галочку в поле «запомнить меня», то сервер устанавливает печеньку с достаточно большим сроком годности: от месяца до нескольких лет, в зависимости от сервиса.
К сожалению, чем больше срок годности печеньки, тем выше вероятность так называемой кражи сессии: угон злоумышленником ключа сессии, хранящегося в печеньке. Угон ключа сессии позволяет злоумышленнику выполнять действия от имени пользователя. По этой причине такие сервисы, как платежные системы и личные кабинеты на сайтах банков не предоставляют механизма «запомнить меня». Более того, часто они вводят дополнительную защиту, например отправляют специальный код на мобильный телефон пользователя для подтверждения его личности.
Сессия и печеньки
Сессия состоит из ключа
— некоторого уникального набора символов — и
данных
. В самом простом случае данные сессии ограничиваются идентификатором пользователя из базы данных. Но часто там хранится и множество других данных, которые должны быть уничтожены, как только пользователь покинет систему: неоплаченный заказ из корзины, текст неотправленного комментария, неопубликованная фотография и т.д.
Когда сессия создана, ее ключ отправляется пользователю (сами данные сессии никогда не покидают сервер). Впоследствии браузер пользователя отправляет его на сервер при каждом запросе. Это упрощает повторную идентификацию пользователя в пределах одной сессии. Без этого механизма пользователю пришлось бы авторизоваться при переходе со страницы на страницу.
Отправка ключа сессии браузером осуществляется с помощью механизма, называемого печеньками
(
Cookies
). Для каждой печеньки сервер может установить
срок годности
, по истечении которого браузер пользователя автоматически уничтожает печеньку. Если срок годности не указан, она уничтожается как только пользователь закрывает окно браузера.
Создаём нужные страницы
Перед тем, как выложим чанки с фрагментами кода, создадим пять страниц.
- Страница входа (1):
страница, содержащая форму входа - Сброс пароля (2):
страница, где пользователи могут запросить восстановление пароля - Сброс пароля, обработчик (3):
скрытая страница, которая будет на самом деле сбрасывать пароль. Пользователи её видеть не будут. - Страница только для пользователей (4):
страница, содержимое которого видят только авторизованные пользователи сайта - Страница выхода (5):
страница, на которую переадресовывается пользователь после успешного выхода
Вот так у меня выглядит дерево ресурсов в данный момент. Имейте виду, что у вас ID ресурсов будет другой. В данном примере ничего кроме страниц для компонента «Login» нету.
Дальше, нам нужно назначить правильные права для пользователей и ресурсов.
Создаём необходимые группы пользователей и группы ресурсов
MODX Revo имеет очень гибкую систему детализации прав, когда дело доходит до разрешений для пользователей, но в данной теме мы сделаем только то, что нам нужно не заходя глубоко в тему. И так, приступим. 1. Безопасность → Группы Ресурсов
Нажимаем на «Создать группу ресурсов» и называем её «Только для пользователей», например. Нажимаем «Сохранить» и всё, на данной странице больше ничего не меняем.
2. Безопасность → Контроль доступа
На первой вкладке «Группы пользователя» нажимаем на «Новая группа пользователей». Новую группу назовём «Пользователи» и нажмём «Сохранить». Группа пользователей будет иметь доступ к ресурсам «Только для пользователей». Зачем это нам, мы узнаем чуть позже в рамках этого урока. 3. На этой же странице (
Безопасность → Контроль доступа
), щелкнём правой кнопкой мыши на созданную группу пользователей и выберем «Редактировать группу пользователей». Далее переходим на вкладку «Доступ к группам ресурсов» и щёлкаем на «Добавить группу ресурсов». Для правильной работы, должны быть как минимум такие параметры:
Группа ресурсов:
Только для пользователей (тот, который мы только что создали)
Минимальная роль:
Member-9999
Политика доступа:
Load, List and View
Контекст:
web И сохраняем.
4. Безопасность → Управление пользователями
Создаём «нового пользователя» и тем самым проверяем, как будет работать разграничение прав доступа для пользователей. В данном случае, используйте простой логин и пароль, ибо нам, как я уже выше писал — важно убедиться в том, что новый пользователь находится в группе «Пользователи». Для этого переходим на вкладку «Права доступа» и нажимаем на «Добавить пользователя в группу».
Группа пользователя:
Пользователи
Роль:
Member Затем нажимаем «сохранить» у окошка, а затем
ещё раз
в правом углу панели управления. Это должно нам гарантировать, что новый пользователь может войти в систему с правами «Пользователя», чтобы смотреть страницу с правами «Только для пользователей». Теперь вернёмся назад к страницам, дабы добавить сниппеты и фрагменты кода на соответствующие страницы.