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

Rails生产环境通过LinkedIn注册用户报BCrypt::Errors::InvalidHash错误

生产环境LinkedIn OmniAuth注册500错误排查

错误现象

生产站点通过LinkedIn完成用户授权回调时,直接返回500内部服务错误,本地开发环境全流程运行无异常。生产端输出的错误日志栈如下:

Completed 500 Internal Server Error in 202ms (ActiveRecord: 3.8ms)
[f842309d-189a-4edc-bafb-a6a567ea8fcd]
[f842309d-189a-4edc-bafb-a6a567ea8fcd] *BCrypt::Errors::InvalidHash (invalid hash)*:
[f842309d-189a-4edc-bafb-a6a567ea8fcd]
[f842309d-189a-4edc-bafb-a6a567ea8fcd] app/models/user.rb:82:in `from_omniauth'
[f842309d-189a-4edc-bafb-a6a567ea8fcd] app/controllers/users/omniauth_callbacks_controller.rb:9:in `linkedin'

现有实现代码

OmniAuth回调控制器

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
    def linkedin
      @user = User.from_omniauth(auth_hash, current_user, session)
      remember_me(@user)
      session[:user_id] = @user.id
      if !(@user.img_url == auth_hash.info.picture_url)
        @user.update_attributes(:img_url => auth_hash.info.picture_url)
      end
      if not @user.persisted?
        session["devise.linkedin_data"] = auth_hash
        render body: session.to_json
      end
      sign_in_and_redirect(:user, @user)
    end
end

User模型from_omniauth方法核心逻辑

def self.from_omniauth(auth, signed_in=nil, session)
  user.password = Devise.friendly_token unless user.encrypted_password.present?
end

错误产生原因

BCrypt::Errors::InvalidHash 错误的触发机制非常明确:Devise内部在执行密码校验逻辑时,会直接读取用户数据库中encrypted_password字段的值,交给BCrypt做格式合法性校验。如果这个字段的值不符合BCrypt哈希的标准格式,就会直接抛出该错误。

本地开发和生产环境行为不一致的核心诱因有两点:

  • 生产数据库存在历史脏数据:比如早期数据导入脚本、手动数据库修改、旧版本兼容逻辑写入的异常值,这些值的格式不满足BCrypt哈希要求(比如长度不足60位、字符串被截断、非哈希格式的明文文本),本地开发库没有这类脏数据,因此测试时不会触发报错。
  • 现有from_omniauth方法的判断逻辑存在缺陷:代码仅通过encrypted_password.present?判断是否需要为OmniAuth注册用户生成随机密码,完全不校验该字段的值是不是合法的BCrypt哈希。只要字段存在值,哪怕是乱码、不合法的字符串,就会跳过生成合法密码的步骤,后续Devise执行登录状态校验、密码比对流程时,就会触发BCrypt解析失败,直接返回500错误。

对应修复方案

  • 第一,先清理生产库的异常数据:查询全量用户的encrypted_password字段,筛选出长度不符合BCrypt固定长度(60位)的脏数据,将这些异常值清空,或为对应用户写入合法的随机Devise密码哈希。
  • 第二,修正from_omniauth方法的判断逻辑,不要仅靠字段是否为空做判断,增加哈希格式校验:
    把原有逻辑:
    user.password = Devise.friendly_token unless user.encrypted_password.present?
    
    替换为带格式校验的判断:
    # 仅当密码字段为空,或是哈希格式不合法时,才自动生成随机密码
    unless user.encrypted_password.present? && Devise::Encryptor.compare(User, user.encrypted_password, "占位校验字符串")
      user.password = Devise.friendly_token
    end
    
    Devise::Encryptor.compare方法会在检测到哈希格式不合法时直接抛出InvalidHash错误,被判断条件捕获,自动触发合法密码的赋值逻辑,从根源上避免后续BCrypt解析失败。
  • 第三,补充流程兜底:在OmniAuth回调流程中,如果检测到匹配用户的加密密码格式不合法,优先在模型层完成合法密码的补全,不要等到登录流程触发底层解析才报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 10:45:00