Является ли этот API-интерфейс аутентификации Rails JSON (с использованием Devise) безопасным?
Приложение My Rails использует Devise для аутентификации. У него есть родственное приложение 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
И вконфиг / routes.rb:
namespace :api, defaults: { format: "json" } do
devise_for :users
end
Я немного не в себе, и я уверен, что здесь есть кое-что, на что мое будущее я оглянется и съежится (обычно это так). Некоторые сомнительные части:
во-первыхвы заметите, что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-атак, но добавление этой строки было единственным способом, которым я мог заставить эту чертову работу работать
в-третьихЯ хочу убедиться, что я понимаю, как работает аутентификация, как только пользователь вошел в систему. Скажем, у меня есть вызов APIGET /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
.Спасибо!