В приложении My Rails используется функция "Усовершенствование" для аутентификации. У него есть приложение для сестры iOS, и пользователи могут войти в приложение iOS, используя те же учетные данные, которые они используют для веб-приложения. Поэтому мне нужен какой-то API для аутентификации.
Здесь много похожих вопросов указывают на этот учебник, но он кажется устаревшим, поскольку token_authenticatable
модуль с тех пор был удален из Devise, а некоторые из строк выдают ошибки. (Я использую Devise 3.2.2.) Я попытался опрокинуть свой собственный на основе этого учебника (и этот), но Я не уверен в этом на 100% - я чувствую, что может быть что-то, что я неправильно понял или пропустил.
Во-первых, следуя рекомендациям этой сути, я добавил текстовый атрибут authentication_token
в таблицу users
и следующее до user.rb
:
before_save :ensure_authentication_token
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.find_by(authentication_token: token)
end
end
Тогда у меня есть следующие контроллеры:
api_controller.rb
class ApiController < ApplicationController
respond_to :json
skip_before_filter :authenticate_user!
protected
def user_params
params[:user].permit(:email, :password, :password_confirmation)
end
end
(Обратите внимание, что мой application_controller
имеет строку before_filter :authenticate_user!
.)
апи /sessions _controller.rb
class Api::SessionsController < Devise::RegistrationsController
prepend_before_filter :require_no_authentication, :only => [:create ]
before_filter :ensure_params_exist
respond_to :json
skip_before_filter :verify_authenticity_token
def create
build_resource
resource = User.find_for_database_authentication(
email: params[:user][:email]
)
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user][:password])
sign_in("user", resource)
render json: {
success: true,
auth_token: resource.authentication_token,
email: resource.email
}
return
end
invalid_login_attempt
end
def destroy
sign_out(resource_name)
end
protected
def ensure_params_exist
return unless params[:user].blank?
render json: {
success: false,
message: "missing user parameter"
}, status: 422
end
def invalid_login_attempt
warden.custom_failure!
render json: {
success: false,
message: "Error with your login or password"
}, status: 401
end
end
апи /registrations _controller.rb
class Api::RegistrationsController < ApiController
skip_before_filter :verify_authenticity_token
def create
user = User.new(user_params)
if user.save
render(
json: Jbuilder.encode do |j|
j.success true
j.email user.email
j.auth_token user.authentication_token
end,
status: 201
)
return
else
warden.custom_failure!
render json: user.errors, status: 422
end
end
end
И в config/routes.rb:
namespace :api, defaults: { format: "json" } do
devise_for :users
end
Я немного из глубины, и я уверен, что здесь есть что-то, что мое будущее будет оглядываться назад и сжиматься (обычно есть). Некоторые части iffy:
Во-первых,, вы заметите, что Api::SessionsController
наследует от Devise::RegistrationsController
, тогда как Api::RegistrationsController
наследует от ApiController
(у меня также есть некоторые другие контроллеры, такие как Api::EventsController < ApiController
, которые имеют дело с большим количеством стандартный материал REST для моих других моделей и не имеет большого контакта с Devise.) Это довольно уродливое соглашение, но я не мог найти другого способа получить доступ к методам, которые мне нужны в Api::RegistrationsController
. Учебник, связанный с выше, имеет строку include Devise::Controllers::InternalHelpers
, но этот модуль, кажется, был удален в более поздних версиях Devise.
Во-вторых,, я отключил CSRF-защиту с помощью строки skip_before_filter :verify_authentication_token
. У меня есть сомнения относительно того, хорошая ли это идея - я вижу много конфликтующих или трудно понять советы о том, являются ли API-интерфейсы JSON уязвимыми для атак CSRF, но добавив, что эта линия была единственным способом, которым я мог бы получить эту чертову работу.
В-третьих,, я хочу убедиться, что я понимаю, как работает аутентификация после входа пользователя. Скажем, у меня есть API-вызов GET /api/friends
, который возвращает список текущих друзей пользователя. Насколько я понимаю, приложение iOS должно было бы получить пользователя authentication_token
из базы данных (что является фиксированным значением для каждого пользователя, который никогда не меняется?), А затем отправить его как параметр вместе с каждым запросом, например. GET /api/friends?authentication_token=abcdefgh1234
, тогда мой Api::FriendsController
мог бы сделать что-то вроде User.find_by(authentication_token: params[:authentication_token])
, чтобы получить current_user. Это действительно так просто, или я чего-то не хватает?
Так что для тех, кому удалось прочесть весь путь до конца этого мамонтового вопроса, спасибо за ваше время! Подводя итог:
Devise::RegistrationsController
, а остальные - от ApiController
.Спасибо!
Вы не хотите отключать CSRF, я читал, что люди почему-то не относятся к API-интерфейсам JSON, но это недоразумение. Чтобы его включить, вы хотите внести несколько изменений:
на стороне сервера добавьте after_filter к контроллеру сессий:
after_filter :set_csrf_header, only: [:new, :create]
protected
def set_csrf_header
response.headers['X-CSRF-Token'] = form_authenticity_token
end
Это создаст токен, поместит его в ваш сеанс и скопирует в заголовок ответа для выбранных действий.
клиентская сторона (iOS) вам нужно убедиться, что две вещи на месте.
вашему клиенту необходимо отсканировать все ответы сервера для этого заголовка и сохранить его при его передаче.
... get ahold of response object
// response may be a NSURLResponse object, so convert:
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
// grab token if present, make sure you have a config object to store it in
NSString *token = [[httpResponse allHeaderFields] objectForKey:@"X-CSRF-Token"];
if (token)
[yourConfig setCsrfToken:token];
наконец, вашему клиенту необходимо добавить этот токен ко всем запросам "не GET", которые он отправляет:
... get ahold of your request object
if (yourConfig.csrfToken && ![request.httpMethod isEqualToString:@"GET"])
[request setValue:yourConfig.csrfToken forHTTPHeaderField:@"X-CSRF-Token"];
Заключительная часть головоломки заключается в понимании того, что при входе в систему используются два последующих сеанса/csrf-токены. Входной поток будет выглядеть так:
GET /users/sign_in ->
// new action is called, initial token is set
// now send login form on callback:
POST /users/sign_in <username, password> ->
// create action called, token is reset
// when login is successful, session and token are replaced
// and you can send authenticated requests
auth_token
в систему мне нужно получить auth_token
из ответа, а затем передать его вместе с последующими запросами на аутентификацию от имени этого пользователя?
Ваш пример, похоже, имитирует код из блога Devise - https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
Как упоминалось в этой статье, вы делаете это подобно варианту 1, который, по их словам, является небезопасным. Я думаю, что ключ заключается в том, что вы не хотите просто reset токен аутентификации каждый раз, когда пользователь будет сохранен. Я думаю, что токен должен быть создан явно (каким-то TokenController в API) и должен истекать периодически.
Вы заметите, что я говорю "я думаю", поскольку (насколько я могу судить) никто больше не знает об этом.
Топ-10 наиболее распространенных уязвимостей в веб-приложениях описаны в OWASP Top 10. В этом вопросе было указано, что защита от перекрестного запроса запросов (CSRF) отключена, а CSRF находится в верхней части OWASDP. Короче говоря, CSRF используется злоумышленниками для выполнения действий в качестве аутентифицированного пользователя. Отключение защиты CSRF приведет к уязвимостям с высоким уровнем риска в приложении и подрывает цель использования безопасной системы проверки подлинности. Вероятно, что защита CSRF не срабатывала, поскольку клиент не прошел токен синхронизации CSRF.
Прочитайте весь топ-10 OWASP, не получив при этом чрезвычайно опасный. Обратите внимание на "Сломанная аутентификация и управление сеансами" , также проверьте "Управление сеансом" Cheat Sheet.
Api::SessionsController
расширяется отDevise::RegistrationsController
.