В приложении 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. Это действительно так просто, или я чего-то не хватает?
Так что для тех, кому удалось прочесть весь путь до конца этого мамонтового вопроса, спасибо за ваше время! Подводя итог:
- Безопасна ли эта система входа в систему? Или есть что-то, что я упустил или неправильно понял, например. когда дело доходит до атак CSRF?
- Является ли мое понимание того, как аутентифицировать запросы, когда пользователи подписаны правильно? (см. "в-третьих..." выше.)
- Можно ли очистить или улучшить этот код? В частности, уродливая конструкция, состоящая в том, что один контроллер наследует от
Devise::RegistrationsController
, а остальные - отApiController
.
Спасибо!