Эта статья была рецензирована Томом Паркином . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
За последние несколько месяцев я написал несколько тем, охватывающих различные решения для аутентификации, от низкоуровневого (AuthLogic) до полноценных (Devise). Сегодня мы еще раз обсудим OAuth 2 , однако на этот раз я покажу вам, как создать свой собственный поставщик аутентификации и настроить его по мере необходимости.
Сегодняшний гость — oPRO (сокращение от O Auth Pro vider), созданное Ричардом Шниманом. Это движок Rails , позволяющий быстро и легко создать свой собственный поставщик аутентификации. OPRO поставляется с кучей предустановленных настроек, контроллеров, представлений и моделей. Большинство из этих предметов могут быть расширены или изменены в дальнейшем.
Если по какой-то причине вы никогда не работали с протоколом OAuth 2, вы можете прочитать это вводное руководство или обратиться к моей статье по использованию OAuth 2 с Rails . Проще говоря, это протокол, который позволяет некоторым сторонним приложениям получать ограниченный доступ к сервису от имени пользователя. Круто то, что пользователь может контролировать, какие действия может выполнять приложение, не имея доступа к паролю пользователя.
oPRO делает только два предположения о вашей настройке:
- У вас есть какая-то пользовательская модель для аутентификации
- Вы используете ActiveRecord
Чтобы увидеть его в действии и прочитать некоторые документы, ознакомьтесь с этим примером приложения .
Эта статья будет состоять из трех частей. Мы рассмотрим много разных вещей:
- Подготовка провайдера аутентификации (назовем его «серверное приложение»)
- Подготовка демонстрационного клиентского приложения («клиентское приложение»)
- Настройка базовой аутентификации на сервере приложения
- Интеграция OPRO
- Обеспечение базового рабочего процесса OAuth
- Настройка представлений и контроллеров по умолчанию
- Кодирование простого API-адаптера
- Добавление пользовательских данных в хеш аутентификации
- Работа с областью
- Добавление функциональности для обновления токенов
- Ограничение скорости
- Представляем индивидуальное решение для аутентификации для серверного приложения
- Обмен учетными данными пользователя для токена доступа
Это похоже на большую работу, но я буду там, чтобы вести вас по пути, так что не бойтесь!
Исходный код для сервера и клиентских приложений можно найти на GitHub .
Подготовка серверного приложения
Сделайте глубокий вдох и создайте новое приложение Rails. Это будет наш поставщик аутентификации:
$ rails new OproServer -T
Для этой демонстрации я использую Rails 4.2, но oPRO также совместим с Rails 3.1 и 3.2.
Сейчас нам потребуются два камня:
Gemfile
[...] gem 'opro' gem 'devise' [...]
OPRO предлагает Devise в качестве решения по умолчанию для аутентификации, поэтому мы будем придерживаться его (однако я дам вам несколько инструкций по использованию различных механизмов аутентификации).
Бегать
$ bundle install
Теперь давайте настроим Devise. Я не собираюсь давать глубокое объяснение, но если вы хотите узнать больше, обратитесь к этой статье .
Запустите генераторы devise, чтобы обеспечить некоторую базовую конфигурацию и создать модель User
:
$ rails generate devise:install $ rails generate devise User
При необходимости настройте файл config / initializers / devise.rb . Кроме того, измените макет для отображения флеш-сообщений, поскольку Devise полагается на них:
просмотров / макеты / application.html.erb
[...] <% flash.each do |key, value| %> <div class="alert alert-<%= key %>"> <%= value %> </div> <% end %> [...]
Для этой демонстрации я не собираюсь использовать какой-либо стиль, потому что у нас много других дел. Теперь запустите другой генератор, чтобы создать файл инициализатора и смонтировать маршруты для oPRO:
$ rails g opro:install
Наконец примените все миграции:
$ rake db:migrate
Регистрация нового клиентского приложения
По умолчанию oPRO поставляется со страницей документации, поэтому загрузите свой сервер и перейдите к localhost: 3000 / oauth_docs для доступа к нему. Если он не отображается, убедитесь, что вы ничего не пропустили из предыдущего раздела.
Когда вы будете готовы, создайте статический контроллер страниц, корневой маршрут и представление:
pages_controller.rb
class PagesController < ApplicationController end
конфиг / routes.rb
[...] root to: 'pages#index' [...]
просмотров / страниц / index.html.erb
<h1>Welcome to my Auth provider!</h1> <%= link_to 'Register a new client app', new_oauth_client_app_path %>
Эта ссылка ведет на страницу, где клиенты могут зарегистрировать свои приложения, как они делают это при получении пары ключей в Twitter или Facebook. Вам будет предложено пройти аутентификацию при посещении этой страницы, поэтому зарегистрируйте пример пользователя. На странице «Создание клиентского приложения OAuth» введите имя своего приложения и нажмите «Создать» (имя можно изменить позже). Вам будет представлен идентификатор клиента и пара секретных ключей, поэтому пока оставьте эту страницу открытой.
Следующее, что нам нужно сделать, это создать клиентское приложение, так что перейдите к следующему шагу!
Подготовка клиентского приложения
Создайте еще одно приложение Rails под названием OproClient
:
$ rails new OproClient -T
Нам нужно место для хранения нашего идентификатора клиента и секретных ключей, полученных на предыдущем шаге. Конечно, мы можем поместить это прямо в код, но это не очень безопасно, поэтому я буду придерживаться переменных среды. В режиме разработки значения будут загружены из файла config / local_env.yml :
конфиг / local_env.yml
opro_client_id: 'your_id' opro_client_secret: 'your_secret' opro_base_url: 'http://localhost:3000'
Для удобства я также включил базовый URL сервера.
Теперь настройте файл application.rb для правильной установки ENV
:
конфиг / application.rb
[...] if Rails.env.development? config.before_configuration do env_file = File.join(Rails.root, 'config', 'local_env.yml') YAML.load(File.open(env_file)).each do |key, value| ENV[key.to_s] = value end if File.exists?(env_file) end end [...]
Теперь у вас есть доступ к ENV['opro_client_id']
, ENV['opro_client_secret']
и ENV['opro_base_url']
из вашего кода.
Не забудьте исключить local_env.yml из контроля версий:
.gitignore
[...] config/local_env.yml
Добавьте контроллер статических страниц и приветственную страницу:
pages_controller.rb
class PagesController < ApplicationController end
конфиг / routes.rb
[...] root to: 'pages#index' [...]
просмотров / страниц / index.html.erb
<h1>Welcome!</h1> <%= link_to 'Authenticate via oPRO', "#{ENV['opro_base_url']}/oauth/new?client_id=#{ENV['opro_client_id']}&client_secret=#{ENV['opro_client_secret']}&redirect_uri=%2F%2Flocalhost%3A3001%2Foauth%2Fcallback" %>
Есть несколько вещей, на которые стоит обратить внимание:
-
/oauth/new
— это маршрут oPRO по умолчанию для аутентификации пользователей через OAuth. - Вы должны передать свои
client_id
иclient_secret
для аутентификации для правильной работы (позже мы узнаем, что это не единственный способ). - Вы также должны указать
redirect_uri
— здесь пользователи будут перенаправлены после аутентификации.
Итак, как вы можете видеть, это очень похоже на то, что делают другие провайдеры OAuth 2.
Не уверен насчет вас, но мне этот URL кажется слишком длинным, поэтому я бы хотел переместить его куда-нибудь еще:
application_controller.rb
[...] private def new_opro_token_path "#{ENV['opro_base_url']}/oauth/new?client_id=#{ENV['opro_client_id']}&client_secret=#{ENV['opro_client_secret']}&redirect_uri=%2F%2Flocalhost%3A3001%2Foauth%2Fcallback" end helper_method :new_opro_token_path [...]
Я помещаю его в контроллер, потому что позже нам нужно будет вызывать его и из других действий.
просмотров / страниц / index.html.erb
<h1>Welcome!</h1> <%= link_to 'Authenticate via oPRO', new_opro_token_path %>
Теперь нам нужно настроить URL обратного вызова.
URL обратного вызова
Прежде всего, предоставьте новый маршрут для вашего клиентского приложения:
конфиг / routes.rb
[...] get '/oauth/callback', to: 'sessions#create' [...]
Теперь нам нужен SessionsController
с действием create
. Когда пользователь перенаправляется на это действие, oPRO отправляет параметр кода, поэтому URL будет выглядеть как http://localhost:3001/?code=123
(мы используем порт 3001, поскольку 3000 уже занят сервер — не забывайте об этом!). Затем этот код используется для получения фактического токена доступа путем отправки HTTP-запроса POST на http://localhost:3000/oauth/token.json
вместе с идентификатором клиента и секретом.
Поэтому нам нужна библиотека для выполнения HTTP-запросов. Есть несколько доступных решений, и, конечно, вы можете выбрать свой любимый, но я собираюсь придерживаться rest-client, потому что он прост и все же мощный.
Gemfile
[...] gem 'rest-client' [...]
Бегать
$ bundle install
а теперь создайте новый контроллер:
sessions_controller.rb
class SessionsController < ApplicationController def create response = JSON.parse RestClient.post("#{ENV['opro_base_url']}/oauth/token.json", { client_id: ENV['opro_client_id'], client_secret: ENV['opro_client_secret'], code: params[:code] }, accept: :json) session[:access_token] = response['access_token'] session[:refresh_token] = response['refresh_token'] redirect_to root_path end end
Используя RestClient.post
мы отправляем запрос POST по http://localhost:3000/oauth/token.json
и устанавливаем три обязательных параметра. oPRO отвечает JSON, содержащим токены доступа и обновления, а также поле expires_in
, как скоро токен станет недействительным (по умолчанию он вообще не истекает, поэтому мы отложим его пока).
Затем мы используем общий гем json для анализа ответа и сохранения токенов в сеансе пользователя, перенаправляя их обратно на главную страницу.
Пример запроса API
Пока все хорошо, но нам нужно проверить, что токен действительно работает, выполнив некоторый запрос. К счастью, oPRO поставляется с примером маршрута http://localhost:3000/oauth_tests/show_me_the_money.json
который отвечает сообщением об успешном выполнении, только если токен доступа действителен.
Поэтому создайте новый контроллер:
api_tests_controller.rb
class ApiTestsController < ApplicationController def index redirect_to root_path and return unless session[:access_token] @response = JSON.parse RestClient.get("#{ENV['opro_base_url']}/oauth_tests/show_me_the_money.json", params: { access_token: session[:access_token] }, accept: :json) end end
Если токен доступа не установлен, просто перенаправьте пользователя обратно на главную страницу. В противном случае отправьте запрос GET на образец маршрута, укажите маркер доступа в качестве единственного параметра, а затем проанализируйте ответ.
В соответствующем представлении отобразится сообщение, возвращаемое серверным приложением:
просмотров / api_tests / index.html.erb
<%= @response['message'] %>
Не забудьте добавить маршрут:
конфиг / routes.rb
[...] resources :api_tests, only: [:index] [...]
Наконец, измените вид главной страницы:
просмотров / страниц / index.html.erb
<h1>Welcome!</h1> <% if session[:access_token] %> <%= link_to 'Show some money', api_tests_path %> <% else %> <%= link_to 'Authenticate via oPRO', new_opro_token_path %> <% end %>
Теперь загрузите ваш сервер (не забудьте, что порт 3000 занят):
$ rails s -p 3001
и проверьте, как все это работает. При нажатии на ссылку «Показать немного денег» вы увидите сообщение «OAuth работал!» — это означает, что мы на правильном пути.
Если вы получаете сообщение об ошибке 401, это означает, что вы либо не отправляете токен, либо он недействителен. Дважды проверьте свой код — вы, вероятно, что-то пропустили при выполнении предыдущих шагов.
Настройка контроллеров и представлений
Вы, вероятно, захотите настроить представления или исключить какой-либо контроллер по умолчанию, предоставляемый oPRO (например, контроллер с документами). Это можно сделать, передав хэш аргументов методу mount_opro_oauth
в вашем файле config / rout.rb. Этот метод поддерживает следующие параметры:
-
except
— передать символ или массив символов, чтобы исключить некоторые маршруты. Возможные варианты:-
docs
— контроллер с направляющими -
tests
— контроллер с методами для тестирования API -
client_apps
— контроллер для управления клиентскими приложениями API
-
-
controllers
— передайте хэш с именем контроллера и новым путем. Возможные варианты:-
oauth_docs
— так же, какdocs
-
oauth_tests
— так же, какtests
-
oauth_client_apps
— так же, какclient_apps
-
oauth_new
— контроллер для запроса разрешений у пользователя. Обратите внимание, что вы можете только переопределитьnew
действие. действие по- прежнему будет вызываться из контроллера по умолчанию.
-
Полная реализация этого метода может быть найдена здесь .
Итак, прежде всего, давайте исключим документацию маршрутов. Для этого просто напишите:
конфиг / routes.rb
[...] mount_opro_oauth except: :docs [...]
Что я хочу сделать, так это настроить страницу, которую пользователи видят при аутентификации через наше приложение. Для этого, конечно, нам нужен собственный контроллер.
конфиг / routes.rb
[...] mount_opro_oauth controllers: {oauth_new: 'oauth/auth'}, except: :docs [...]
Мы AuthController
пространство имен AuthController
в Oauth
, поэтому внутри каталога контроллеров создайте папку oauth с файлом auth_controller.rb внутри:
Контроллеры / OAuth / auth_controller.rb
class Oauth::AuthController < Opro::Oauth::AuthController end
Этот пользовательский контроллер наследует от Opro::Oauth::AuthController
определенного oPRO. Я не собираюсь делать какие-либо действия здесь, потому что нам нужно только изменить маршрут. Тем не менее, вполне возможно переопределить new
действие по мере необходимости.
Создайте представление в views / oauth / new.html.erb и настройте его так, как вам нравится. Например, вы можете добавить некоторые пояснения к правам доступа, которые запрашивает приложение:
просмотров / OAuth / new.html.erb
[...] <h2>Explanation</h2> <ul> <li><strong>Read</strong> - the app will be able to read information about your account. This permission is required.</li> <li><strong>Write</strong> - the app will be able to modify your account.</li> </ul>
Может быть, включить некоторые брендинг и контактную информацию здесь. Вы можете использовать тот же подход для настройки других представлений.
Вывод
Итак, в этой части мы заложили основу для следующих шагов. В настоящее время пользователи могут проверять подлинность и выполнять запросы к образцу действий API. Однако предстоит еще много работы: нам нужно хранить информацию о пользователях в базе данных, вводить дополнительные методы API и реорганизовывать код. Кроме того, будет здорово получить некоторую другую информацию о пользователе, кроме токенов.
Поэтому в следующей части этой статьи мы позаботимся обо всех этих проблемах. До скорого!