You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails应用本地登录未下发session ID 外部访问可正常登录故障求助

问题描述

我未修改任何代码,此前正常运行的登录功能突然失效:输入正确的邮箱和密码后仍会跳转回登录页,我没有使用devise组件。

一开始我怀疑是代码问题,但日志显示邮箱密码验证通过,服务端正常发起首页跳转请求。

之后我用本地代理工具Burp Suite追踪浏览器和服务端间的session ID等数据,发现登录成功后服务端未下发session ID,触发authenticate_user校验拦截导致登录失败,希望定位原因并解决。

我还使用ngrok工具暴露本地服务后,外部环境的用户可以正常登录,Burp Suite也显示该场景下session ID下发正常。

由此确认仅本地环境登录时不会下发session ID,近期我未编辑代码,仅执行了以下操作:

  • 安装Git
  • 通过Git将项目"app1"推送到GitHub
  • 安装SourceTree并绑定GitHub账号
  • 通过SourceTree从GitHub克隆"app1",重命名为"app2"

执行上述操作前我在application.rb中添加了如下配置,即便注释该配置问题也未解决:
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.html.erb")
  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,最终跳转回登录页:

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暴露服务后外部用户登录时的抓包结果可见Cookie和Set-Cookie字段均正常存在:
外部登录抓包结果

本地环境登录时的抓包结果可见Cookie和Set-Cookie字段均不存在:
本地登录抓包结果

已尝试的解决方法

重启服务和电脑未解决问题,在Chrome、Firefox、Brave多个浏览器测试均复现该问题。

版本信息

  • ruby 2.6.4p104
  • RubyGems 3.0.3
  • Rails 5.2.3
  • Burp Suite Community Edition 2021.8.2

补充说明

以下操作均未解决问题:

  • 执行bundle exec rake tmp:cache:clear命令
  • 执行rails tmp:clear命令
  • 在users_controller.rb的login_form方法中添加清理session逻辑如下:
def login_form
  reset_session
  session[:user_id] = nil
  session.delete(:user_id)
end

恳请各位帮忙定位问题原因,感谢!


解决方案

根因定位

问题出在代码错误使用caches_action缓存了与会话状态强相关的方法和接口:

  1. application_controller.rb中对set_current_user、authenticate_user、forbid_login_user三个权限校验方法加了动作缓存,缓存生效后方法会直接复用历史结果,不会实时读取当前请求的session值,日志中WHERE users.id IS NULL就是缓存的set_current_user方法返回空用户的直接表现。
  2. users_controller.rb中把login、logout等会话操作接口也加入了缓存列表,登录成功后生成的Set-Cookie响应头会被缓存逻辑过滤,导致本地环境请求时服务端不下发session ID。
  3. ngrok访问正常是因为Rails动作缓存的键包含请求域名,ngrok的域名与localhost不同,对应的缓存未被污染,所以功能正常。你之前操作Git、克隆项目的行为触发了本地缓存的生成,刚好命中了这个代码隐患。

修复步骤

  1. 移除错误的缓存配置:
    • 删除application_controller.rb中的caches_action :set_current_user, :authenticate_user, :forbid_login_user行,权限校验方法绝对不能缓存
    • 清理users_controller.rb中caches_action列表的:login、:logout、:login_form、:ensure_correct_user等和用户状态、权限相关的条目,动作缓存仅可用于无状态的公开页面
  2. 执行全量缓存清理:
    rails tmp:clear
    rails cache:clear
    
  3. 重启Rails服务,本地登录即可恢复正常。

优化建议

你当前配置的protect_from_forgery with: :null_session和skip_before_action :verify_authenticity_token存在CSRF安全风险,如果不是纯API服务建议恢复默认的CSRF校验逻辑。


内容的提问来源于stack exchange,提问作者punpun36

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 14:39:05