当用户尝试登录时,会话 ID 不仅在本地环境中给出

The session ID is not given only in the local environment when a user tries to log in

即使我没有更改任何代码,登录功能也不起作用,本来可以的。即使我输入了正确的电子邮件地址和密码,它也会被重定向到登录页面。 我不使用设计。

首先,我以为某些编码有误,但根据日志,它说电子邮件地址和密码是正确的,并尝试正确重定向到首页。

接下来,我使用了工具 Burp Suite 来跟踪浏览器和服务器(本地代理)之间的会话 ID 等数据。 并且发现登录后没有给session ID,又带来了authenticate_user导致的错误,导致登录失败。 我想找出原因并解决。

此外,我还使用了工具ngrok,让用户可以访问我的本地主机。 然后令人惊讶的是,我本地环境之外的用户能够成功登录。 并且 Burp Suite 表明会话 ID 在这种情况下被赋予 属性。

因此,只有当我尝试从我的本地环境登录时,才不会提供会话 ID。 最近,我没有编辑任何代码,而是完成了以下这些任务。

在执行这些任务之前,我在下面添加了这段代码。但是即使我注释掉它也不会改变这种情况。

application.rb

config.session_store :cookie_store, expire_after: 8.hours

所有问题都发生在这些任务之后。

代码

routes.rb

get "login" => "users#login_form"
post "login" => "users#login"
post "logout" => "users#logout"

application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
  before_action :set_current_user
  skip_before_action :verify_authenticity_token

  caches_action :set_current_user, :authenticate_user, :forbid_login_user

  def set_current_user
    @current_user = User.find_by(id: session[:user_id])
  end

  def authenticate_user
    if @current_user == nil
      flash[:notice] = "You need to log in"
      redirect_to("/login")
    end
  end

  def forbid_login_user
    if @current_user
      flash[:notice] = "You are already logged in"
      redirect_to("/")
    end
  end
end

users_controller.rb

before_action :authenticate_user, {except: [:new, :create, :login_form, :login]}
before_action :forbid_login_user, {only: [:new, :create, :login_form, :login]}
before_action :ensure_correct_user, {only: [:edit, :update]}
before_action :ensure_correct_user_account, {only: [:setting_password, :update_password, :setting_email, :update_email, :delete_account, :destroy]}
caches_action :index, :show, :follow, :new, :create, :edit, :user_params, :update, :destroy, :login_form, :login, :logout, :likes, :ensure_correct_user, :followings, :followers, :top

def login
  @user = User.find_by(email: params[:email])
  if @user && @user.authenticate(params[:password])
    session[:user_id] = @user.id
    flash[:notice] = "You have successfully logged in"
    redirect_to("/")
  else
    @error_message = "Email address or password is incorrect"
    @email = params[:email]
    @password = params[:password]
    render("users/login_form")
  end
end

def login_form
end

def logout
  session[:user_id] = nil
  flash[:notice] = "You have successfully logged out"
  redirect_to("/login")
end  

当用户成功登录后,重定向到“/”(首页)。

日志

这是我尝试登录时的日志,看到Started GET "/",用户似乎登录成功了。但随后日志显示 Filter chain halted as :authenticate_user rendered or redirected,并重定向到 "/login".

Started POST "/login" for ::1 at 2021-08-24 14:44:31 +0900
Processing by UsersController#login as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"JBcKtTsdCT0JXgyIfzjKVqi/8KEz4pRmUXB2Kybn8eHcnz7UKXoMsbBRkBCnoUqwnIdi4hUkZ/6oQKFVqMyG/g==", "email"=>"hoge", "password"=>"[FILTERED]"}
  User Load (35.1ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
  ↳ app/controllers/application_controller.rb:9
  User Load (0.6ms)  SELECT  `users`.* FROM `users` WHERE `users`.`email` = 'hoge' LIMIT 1
  ↳ app/controllers/users_controller.rb:255
Redirected to http://localhost:3000/
Completed 302 Found in 422ms (ActiveRecord: 41.9ms)


Started GET "/" for ::1 at 2021-08-24 14:44:32 +0900
Processing by UsersController#top as HTML
  User Load (0.6ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
  ↳ app/controllers/application_controller.rb:9
Redirected to http://localhost:3000/login
Filter chain halted as :authenticate_user rendered or redirected
Completed 302 Found in 6ms (ActiveRecord: 0.6ms)


Started GET "/login" for ::1 at 2021-08-24 14:44:32 +0900
Processing by UsersController#login_form as HTML
  User Load (0.5ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
  ↳ app/controllers/application_controller.rb:9
  Rendering users/login_form.html.erb within layouts/application
  Rendered users/login_form.html.erb within layouts/application (0.9ms)
  CACHE User Load (0.1ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
  ↳ app/views/layouts/application.html.erb:51
Completed 200 OK in 244ms (Views: 233.9ms | ActiveRecord: 0.5ms)

Burp Suite 的展示

我使用ngrok时用户从外部登录时会话ID是如何给出的。您可以看到同时显示了“Cookie”和“Set-Cookie”。

当我尝试从本地环境登录时。 “Cookie”和“Set-Cookie”都看不到。

我试过的

重启服务器和我的电脑都没有解决问题。 多个浏览器(Google Chrome、Firefox 和 Brave)显示相同的结果。

版本

ruby 2.6.4p104 红宝石宝石 3.0.3 Rails5.2.3 Burp Suite 社区版 2021.8.2

后记

这些作品并没有改变这种情况。

def login_form
  reset_session
  session[:user_id] = nil
  session.delete(:user_id)
end

更好的身份验证方法是:

# app/errors/authentication_error.rb
class AuthenticationError < StandardError; end
class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
  before_action :authenticate_user!
  rescue_from 'AuthenticationError', with: :deny_access

  private 

  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end

  def user_signed_in?
    current_user.present?
  end

  def authenticate_user!
    raise AuthenticationError unless user_signed_in?
  end

  def deny_access
    flash[:notice] = "You need to log in"
    redirect_to login_path
  end
end

不要使用回调来设置当前用户,而是使用记忆其值的方法。这消除了与回调顺序相关的问题的可能性。这应该是唯一知道用户如何在会话中持久化的方法。

使用默认安全方法而不是显式身份验证。这意味着您不会调用 before_action :authenticate_user, {except: [:new, :create, :login_form, :login]},而是调用 skip_before_action :authenticate_user, only: [:new, :create, :login_form, :login] 来选择退出授权。

如果用户未通过身份验证则引发错误将在测试时为您节省大量调试时间,因为您可以禁用 rescue_from 以确保引发错误。

与其将所有内容都塞进一个怪物 UsersController 中,不如创建一个控制器,它只负责让用户登录和注销:

scope controller: 'sessions' do
  get :login, action: :new, as: :login
  post :login, action: :create
  delete :logout, action: :destroy, as: :logout
  get :logout, action: :destroy, as: :logout
end
class SessionsController < ApplicationController
  skip_before_action :authenticate_user!
  before_action :deny_access_to_signed_in_users, if: :user_signed_in?,        

  # GET /login
  def new
    @user = User.new
  end

  # POST /login
  def create
    @user = User.find_by(email: params[:email])
    if @user && @user.authenticate(params[:password])
      session[:user_id] = @user.id
      flash[:notice] = "You have successfully logged in"
      redirect_to root_path
    else
      flash[:notice] = "Email address or password is incorrect"
      @user = User.new(**params.permit(:email, :password))
      render :new
    end
  end

  # DELETE /logout
  def destroy
    session[:user_id] = nil
    flash[:notice] = "You have successfully logged out"
    redirect_to login_path
  end

  private

  def deny_access_to_signed_in_users
    flash[:notice] = "You are already logged in!"
    redirect_to root_path
  end
end
# app/views/sessions/new.html.erb

<h1>Sign in</h1>
<%= form_with(model: @user, path: login_path, method: :post, local: true) do |form| %>
  <div class="field">
    <%= form.label :email %>
    <%= form.email_field :email %>
  </div> 
  <div class="field">
    <%= form.label :password %>
    <%= form.email_field :email %>
  </div> 
  <div class="actions">
    <%= form.submit 'Sign in' %>
  </div>
<% end %>

我能够自己解决问题。 我以前做的文件session_store.rb,就是这个原因

配置 > 初始值设定项 > session_store.rb

app_name = "app_name"
Rails.application.config.session_store :cookie_store, key: "_app_name_session", expire_after: 8.hours, secure: true

secure: true 禁用 http 传递 cookie,所以我无法仅在本地主机上登录。我忘了删除这个文件。

我删除了session_store.rb,在application.rb写了下面的代码。

config.session_store :cookie_store, expire_after: 8.hours, secure: Rails.env.production?

现在,登录功能也适用于本地主机。