Rails 5.2登录成功后Warden获取用户为nil无法加载首页
问题背景
- 部署环境:
ubuntu 18.04 - 异常版本栈:
rails 5.2.8、ruby 2.5.1、devise 4.5.0(所有版本号高于4.5的devise均会触发该问题)、warden 1.2.9 - 历史正常版本栈:
rails 5.0.1、ruby 2.3.3、devise 4.2.0、warden 1.2.7 - 故障现象:用户登录流程执行成功(数据库可查询到登录记录),登录完成重定向到首页时
current_user变为nil/失效,接口返回401未授权,最终跳转回登录页。
日志信息
异常场景日志
[2022-06-02 13:02:21] INFO -- SIGN-IN SIGN-IN SIGN-IN SIGN-IN SIGN-IN SIGN-IN SIGN-IN SIGN-IN SIGN-IN SIGN-IN SIGN-IN SIGN-IN SIGN-IN [2022-06-02 13:02:21] DEBUG -- Warden::Manager.after_authentication: User USER@live.com has signed in. [2022-06-02 13:02:21] INFO -- ^^^^^^^^^ check_reference: request.env['HTTP_REFERER'] = https://SERVER.com/d/users/sign_in params[:controller] = users/sessions [2022-06-02 13:02:21] INFO -- ======================= SessionsController: format.html current_user: #<User signed_in: true, id: 25, email: "USER@live.com", first_name: "FIRST", last_name: "LAST", role: "Administrator", auto_signout_enabled: true, password_changed: false, force_password_reset: false, updated_at: "2022-06-02 20:02:21", created_at: "2020-01-10 13:24:20"> [2022-06-02 13:02:21] INFO -- ======================== application_controller: after_sign_in_path_for [2022-06-02 13:02:21] INFO -- Redirected to http://LOCALAPP/ [2022-06-02 13:02:21] INFO -- ======================= SessionsController: User USER@live.com signed-in from Web browser. [2022-06-02 13:02:21] INFO -- Completed 302 Found in 93ms (ActiveRecord: 4.0ms) [2022-06-02 13:02:21] INFO -- Started GET "/" for 127.0.0.1 at 2022-06-02 13:02:21 -0700 [2022-06-02 13:02:21] INFO -- Processing by RuntimeController#index as HTML [2022-06-02 13:02:21] DEBUG -- Warden::Manager.after_failed_fetch: User nil opts: {:scope=>:user} [2022-06-02 13:02:21] INFO -- Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms) [2022-06-02 13:02:21] INFO -- Warden::Manager.after_failed_fetch: User is nil? true opts: {:scope=>:user} auth: Warden::Proxy:20581780 @config={:default_scope=>:user, :scope_defaults=>{}, :default_strategies=>{:user=>[:database_authenticatable]}, :intercept_401=>false, :failure_app=>#<Devise::Delegator:0x0000000004d66b28>}
正常场景日志
[2022-06-02 16:14:36] INFO -- ********************************* RUNTIME VIEW ********************************* [2022-06-02 16:14:36] DEBUG -- [VIEWS HELPER] get_view_connectivity view_id: 15 <<continue to load home page >>
异常捕获代码
通过如下Warden回调可捕获用户为nil的状态:
Warden::Manager.after_failed_fetch do |user, auth, opts| Rails.logger.debug("Warden::Manager.after_failed_fetch: User is nil? #{user.nil?} opts: #{opts.inspect} auth: #{auth.inspect}".default_warn) end
已尝试方案
- 在rails 5.2.8环境中降级安装
warden 1.2.7,问题未解决 - 尝试降级到
devise 4.2.0,但该版本依赖railties (< 5.1, >= 4.1.0),无法在rails 5.2环境中运行 - 过往遇到相同问题只能回退全量旧版本,但旧版本Ruby Gem维护成本极高,新增、升级Gem时频繁出现版本兼容问题,调试耗时极长
可行修复方案(无需回退版本)
1. 修正session cookie配置
Rails 5.2默认新增了cookie安全校验规则,配置错误会导致重定向时session丢失,修改config/initializers/session_store.rb:
Rails.application.config.session_store :cookie_store, key: '_your_app_session', # 替换为自身应用的session key domain: :all, # 多子域部署必填,单域部署可直接写对应域名,禁止留空 tld_length: 2, # 域名层级为3级(如a.b.com)设为2,多级域名(如a.b.c.com)对应调整数值 same_site: :lax, # 禁止设为:strict,否则跨重定向场景浏览器会拦截cookie secure: Rails.env.production? # HTTPS环境设为true,纯HTTP环境必须设为false
2. 重写Warden序列化逻辑兼容Devise 4.5+规则
Devise 4.5之后修改了用户对象序列化到session的默认逻辑,如果User模型存在不可序列化的自定义属性,会导致反序列化失败返回nil,在config/initializers/devise.rb中添加如下配置,强制使用用户ID做序列化:
config.warden do |manager| manager.serialize_into_session(&:id) manager.serialize_from_session { |id| User.find_by(id: id) } end
3. 兼容Rails 5.2 cookie签名算法升级
Rails 5.2默认使用SHA256作为cookie签名算法,从旧版本升级时会存在旧cookie无法解析的问题,在config/application.rb中添加兼容配置:
config.action_dispatch.use_authenticated_cookie_encryption = true config.action_dispatch.signed_cookie_digest = "SHA256" # 升级过渡期添加,兼容旧版本生成的SHA1签名cookie,等所有旧cookie自然过期后可删除 config.action_dispatch.cookies_rotations.tap do |cookies| cookies.rotate :signed, Rails.application.credentials.secret_key_base, digest: "SHA1" end
4. 排查CSRF校验重置session问题
如果CSRF token校验失败,Rails会自动重置session导致登录态丢失,在app/controllers/application_controller.rb中添加异常捕获排查:
rescue_from ActionController::InvalidAuthenticityToken do |e| Rails.logger.error "CSRF校验失败,session已重置: #{e.message}, referer: #{request.referer}" redirect_to new_user_session_path end
内容的提问来源于stack exchange,提问作者djm
相关产品推荐
相关产品推荐

