#ruby-on-rails #devise
#ruby-on-rails #разработка
Вопрос:
На моем локальном компьютере есть два приложения rails, приложение A и приложение B. Приложение A перенаправляет посетителя в приложение B с помощью токена входа, который он ранее получил из приложения A (simple_token_authentication). URL-адрес выглядит примерно так "/offers/F6MyubLfP5dKcrGLp?project_id=337amp;user_id=93amp;user_token=6s3xaoxRLNyJ8VG7wqEF"
. В большинстве случаев это работает нормально. Аутентификация прошла успешно, и пользователь видит правильную страницу в приложении B.
Но иногда пользователь будет перенаправлен в приложение A. Я не знаю, почему и где я должен начать с отладки.
Приложение B регистрирует, когда получает URL-адрес для входа и перенаправляет (найдено 302) обратно в приложение A:
Started GET "/offers/F6MyubLfP5dKcrGLp?project_id=337amp;user_id=93amp;user_token=6s3xaoxRLNyJ8VG7wqEF" for ::1 at 2020-10-07 10:53:40 0200
Processing by OffersController#show as HTML
Parameters: {"project_id"=>"337", "user_id"=>"93", "user_token"=>"6s3xaoxRLNyJ8VG7wqEF", "id"=>"F6MyubLfP5dKcrGLp"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 92], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:58:in `require_login'
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 93], ["LIMIT", 1]]
(0.3ms) BEGIN
↳ app/controllers/application_controller.rb:65:in `after_successful_token_authentication'
User Exists? (0.5ms) SELECT 1 AS one FROM "users" WHERE "users"."wegateam_contact_id" = $1 AND "users"."id" != $2 LIMIT $3 [["wegateam_contact_id", 233], ["id", 92], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:65:in `after_successful_token_authentication'
(0.6ms) SELECT COUNT(*) FROM "users" WHERE "users"."authentication_token" = $1 [["authentication_token", "vDQWLATe_x5_V3AxL7ep"]]
↳ app/controllers/application_controller.rb:65:in `after_successful_token_authentication'
User Update (0.6ms) UPDATE "users" SET "authentication_token" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["authentication_token", "vDQWLATe_x5_V3AxL7ep"], ["updated_at", "2020-10-07 08:53:40.483669"], ["id", 92]]
↳ app/controllers/application_controller.rb:65:in `after_successful_token_authentication'
(0.4ms) COMMIT
↳ app/controllers/application_controller.rb:65:in `after_successful_token_authentication'
Redirected to http://localhost:5000/
Completed 302 Found in 98ms (ActiveRecord: 3.1ms | Allocations: 10039)
Аутентификация, похоже, прошла успешно, потому что выполняется метод after_successful_token_authentication
simple_token_authentication.
# overwrite simple_token_authentication hook: token will only be valid for one login
def after_successful_token_authentication
current_user.update(authentication_token: nil)
# Token will renewed when saving
end
Когда он работает должным образом, http-ответ выглядит следующим образом:
Когда нежелательное перенаправление будет выполнено, оно выглядит следующим образом:
Когда я вручную копирую и вставляю URL-адрес после нежелательного перенаправления, вход в систему работает нормально.
Похоже, что проблема возникает только тогда, когда пользователь App B был создан незадолго до этого.
Что я пробовал:
- Поскольку пользователь приложения B был создан незадолго до этого (и отправляет токен аутентификации в приложение A), я подумал, что, возможно, devise и simple_token_authentication не готовы к своим действиям, и это приводит к перенаправлению. Итак, я установил тайм-аут в 10 секунд в приложении A, прежде чем оно отправит пользователя на URL-адрес входа в приложение B. Но тот же результат.
Я был бы даже признателен за предложения, где я мог бы начать поиск решений этой проблемы.
ОБНОВЛЕНИЕ: application_controller
class ApplicationController < ActionController::Base
before_action :store_user_location!, if: :storable_location?
before_action :require_login
include Pundit
# Pundit: Ensure, that authorize has been called
# after_action :verify_authorized
# after_action :verify_policy_scoped
# Catch Pundit::NotAuthorizedError
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
# Prevent CSRF attacks by raising an exception.
protect_from_forgery with: :exception, prepend: true
# For simple_token_authentication (Bei Anforderung magic login kann der user nicht eingeloggt sein. In diesem Fall nicht
# authentifizieren)
acts_as_token_authentication_handler_for User, except: :request_magic_login
def user_not_authorized
flash[:error] = "Du hast keine ausreichende Berechtigung diese Seite zu öffnen."
redirect_to(request.referrer || root_path)
end
# Devise Methode überschreiben, um nach Login zu der Seite weiterzuleiten, die der User egentlich aufrufen wollte,
# bevor er auf die Login Seite geleitet wurde.
def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || super
end
private
def redirect_to_project_or_select_page(user)
if user.contact.projects.size != 1
redirect_to user_projects_path(user)
else
redirect_to project_path(user.contact.projects.first.id)
end
end
# Its important that the location is NOT stored if:
# - The request method is not GET (non idempotent)
# - The request is handled by a Devise controller such as Devise::SessionsController as that could cause an
# infinite redirect loop.
# - The request is an Ajax request as this can lead to very unexpected behaviour.
# https://github.com/heartcombo/devise/wiki/How-To:-Redirect-back-to-current-page-after-sign-in,-sign-out,-sign-up,-update
def storable_location?
request.get? amp;amp; is_navigational_format? amp;amp; !devise_controller? amp;amp; !request.xhr?
end
def store_user_location!
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
def require_login
if !current_user amp;amp; !devise_controller? amp;amp; (!request.params[:user_id] || !request.params[:user_token])
redirect_to new_user_session_path
end
end
# simple_token_authentication hook überschreiben: token soll nur für einen Login gelten
def after_successful_token_authentication
current_user.update(authentication_token: nil)
# Wird beim speichern dann neu gesetzt
end
end
Комментарии:
1. Вы выполняете несколько запросов, чтобы найти одного и того же пользователя. Потенциально их можно было бы оптимизировать, но я думаю, что проблема тоже где-то есть. Можете ли вы поделиться тем, для чего используется каждый пользовательский запрос?
2. @TomDunning: первая «загрузка пользователя» инициализируется «require_login» в application_controller. Он проверяет большинство страниц, если пользователь должен войти в систему. Затем при необходимости перенаправляется на страницу входа в систему. Следующие три операции над пользователем были выполнены из этой строки:
current_user.update(authentication_token: nil)
insimple_token_authentication
‘safter_successful_token_authentication
. Это гарантирует, что пользователь становится новым токеном входа, если используется старый. Теперь я не понимаю, почему эта строка приводит к трем операциям с БД.3. @TomDunning: я забыл сказать, require_login не перенаправляет на страницу входа в систему, когда в URL присутствует токен (!request.params[:user_token])
4. Итак, вы используете этот стандартный хук: github.com/gonzalo-bulnes/simple_token_authentication#hooks . Вы проверили, что все серверы, которые могут отправлять этот запрос, разрешены? Если это случайная ошибка, возможно, токены CSRF не выстраиваются в линию для одного из ваших серверов. Если это не так, мне нужно было бы увидеть еще немного кода. В идеале application_controller и какой запасной вариант вы отправили. Я бы также проверил, где установлен current_user, и убедился, что нет условия гонки, при котором очистка этого токена внезапно делает их недействительными. Наконец, я бы указал, на какую страницу они перейдут в случае успеха.
5. @TomDunning Разрешены все серверы? Я так думаю, потому что, когда я повторяю ту же настройку во второй раз с этим пользователем, это работает (кроме того, сервер одинаков для обоих приложений, localhost). Токен CSRF не выстроен в очередь? Я так не думаю, потому что в application_controller
protect_from_forgery with: :exception, prepend: true
возникло бы исключение. Очистка этого токена внезапно делает их недействительными? Вторая попытка с тем же токеном работает. Я добавил application_controller к своему вопросу.