После входа в систему с помощью simple_token_authentication rails перенаправляет на домен референта

#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) in simple_token_authentication ‘s after_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 к своему вопросу.