Ruby on Rails中Devise+OAuth首次登录后current_user为nil问题
current_user为nil的问题排查与解决 问题分析
从你的代码和日志来看,核心矛盾是:首次通过OAuth登录时,after_sign_in_path_for中user_signed_in?返回true,但重定向到问卷页面后,before_action :require_login里的current_user却变成了nil;第二次登录则完全正常。
日志里的User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL说明,Devise在尝试查找ID为NULL的用户——这意味着重定向后的请求会话中没有有效的用户ID,导致current_user无法加载。
根本原因
问题出在你的OmniAuthCallbacksController#linkedin方法的代码顺序上:
你先调用了sign_in_with(内部执行sign_in_and_redirect完成登录并重定向),之后才去修改用户的LinkedIn数据并保存。
这个顺序会导致:
- 浏览器收到重定向响应后,立刻发起问卷页面的请求;
- 此时你的后端还在执行
sign_in_and_redirect之后的用户数据修改操作; - 虽然用户已经被创建,但首次登录时的会话状态可能因后续的数据修改出现异常,或者会话未完全持久化就被重定向请求读取,导致无法识别用户。
解决方案
调整代码顺序,先完成用户和关联数据的所有持久化操作,再执行登录并重定向。同时重构代码逻辑,避免在登录后修改用户数据。
修改后的OmniAuthCallbacksController代码:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController skip_before_action :verify_authenticity_token # 重构sign_in_with,支持传入block在登录前处理用户数据 def sign_in_with(provider_name) @user = User.from_omniauth(request.env["omniauth.auth"]) @user.profile || @user.build_profile # 确保用户关联的Profile存在 # 执行传入的自定义逻辑(比如第三方平台数据同步) yield if block_given? # 确保用户数据完全保存后再登录并重定向 sign_in_and_redirect @user, event: :authentication set_flash_message(:notice, :success, kind: provider_name) if is_navigational_format? end def facebook sign_in_with "Facebook" end def linkedin sign_in_with "LinkedIn" do # 先同步LinkedIn数据到用户模型 @user.linked_in_data = request.env["omniauth.auth"] @user.save! # 用save!确保数据保存成功,避免静默失败 # 同步Profile数据 profile = @user.profile profile.first_name = @user.linked_in_data['info']['first_name'] profile.last_name = @user.linked_in_data['info']['last_name'] profile.title = @user.linked_in_data['info']['description'] profile.industry = @user.linked_in_data['extra']['raw_info']['industry'] profile.save! end end def twitter sign_in_with "Twitter" end def google_oauth2 sign_in_with "Google" end def developer sign_in_with "Developer" end end
额外检查项
确保
User.from_omniauth方法正确保存新用户
检查你的User.from_omniauth方法,确保新创建的用户能成功持久化(比如必填字段如email是否存在):def self.from_omniauth(auth) user = where(provider: auth.provider, uid: auth.uid).first_or_initialize # 确保email字段有值(Devise默认要求) user.email = auth.info.email || "#{auth.uid}@#{auth.provider}.fake" user.save! # 用save!代替save,方便调试保存失败的情况 user end调试会话状态
可以在after_sign_in_path_for和require_login中打印会话内容,确认用户ID是否正确传递:# 在ApplicationController的after_sign_in_path_for中添加 puts "Session after sign in: #{session.inspect}" # 在QuestionnairesController的require_login中添加 puts "Session in require_login: #{session.inspect}"
为什么第二次登录正常?
第二次登录时,用户已经存在于数据库中,from_omniauth直接找到现有用户,即使后续修改数据,会话中的用户ID已经和数据库中的用户完全匹配,因此重定向后的请求能正常加载current_user。
内容的提问来源于stack exchange,提问作者akoinesjr

