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

Ruby on Rails中LinkedIn OpenID登录随机失败排查求助

问题描述

我正在开发一款集成LinkedIn全新OpenID登录功能的应用,该功能大部分时间正常可用,但会随机出现登录失败(每3-10次出现一次),失败时会跳转回根路径。我查阅了各类相关场景,也尝试过在控制器中添加清除Cookie的机制,但均无效果。相关代码及错误日志如下:

相关代码

omniauth_callbacks_controller.rb

# frozen_string_literal: true

class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  before_action :retries

  def linkedin
    # flash[:notice] = I18n.t('omniauth_callbacks.notice.linkedin_callback_initiated')
    login
  end

  def failure
    if @retries < 1
      @retries += 1
      login # Retry the authentication
    else
      redirect_to root_path
    end
  end

  def login
    if auth_hash.nil?
      # puts "Authentication data not received from provider."
      redirect_to root_path
      return
    end
    # flash[:notice] = I18n.t('omniauth_callbacks.notice.processing_linkedin_login', uid: auth_hash[:uid])
    existing_user = User.find_by(uid: auth_hash[:uid])

    if existing_user.present?
      # flash[:notice] = I18n.t('omniauth_callbacks.notice.existing_user_found', uid: auth_hash[:uid])
      sign_in_existing_user(existing_user)
    else
      # flash[:notice] = I18n.t('omniauth_callbacks.notice.no_existing_user')
      create_or_sign_in_user
    end
  end

  private

  def retries
    @retries ||= 0
  end

  def auth_hash
    request.env['omniauth.auth'].tap do |auth|
      # flash[:notice] = I18n.t('omniauth_callbacks.notice.auth_hash_received', auth: auth.inspect)
    end
  end

  def sign_in_existing_user(user)
    if user.provider == auth_hash[:provider]
      # flash[:notice] = I18n.t('omniauth_callbacks.notice.signing_in_existing_user', email: user.email)
      sign_in_and_redirect(user)
    else
      flash[:alert] = I18n.t('omniauth_callbacks.alert.user_exists_different_provider', email: user.email)
      redirect_to root_path
    end
  end

  def create_or_sign_in_user
    user = User.from_omniauth(auth_hash).tap do |new_user|
      # flash[:notice] = I18n.t('omniauth_callbacks.notice.user_persisted', email: new_user.email)
    end

    if user.persisted?
      handle_persisted_user(user)
    else
      handle_nonpersisted_user(user)
    end
  end

  def handle_persisted_user(user)
    # flash[:notice] = I18n.t('omniauth_callbacks.notice.user_persisted', email: user.email)
    sign_in_and_redirect(user)
    # set_flash_message(:notice, :success, kind: 'LinkedIn') if is_navigational_format?
  end

  def handle_nonpersisted_user(user)
    flash[:alert] = I18n.t('omniauth_callbacks.alert.unable_to_sign_in', errors: user.errors.full_messages.join(', '))
    redirect_to root_path
  end
end

Devise.rb配置

config.skip_session_storage = [:http_auth, :linkedin]
config.omniauth :linkedin, ENV.fetch('LINKEDIN_CLIENT_ID', nil) || Rails.application.credentials[:linkedin_id], ENV.fetch('LINKEDIN_CLIENT_SECRET', nil) || Rails.application.credentials[:linkedin_key], :scope => 'openid profile email'

Gemfile

gem 'oauth2', '~> 2.0', '>= 2.0.9'
gem "omniauth", "~> 2.1.2"
gem 'omniauth-linkedin-openid', '~> 1.0.1'
gem 'omniauth-oauth2', '~> 1.8'
gem "omniauth-rails_csrf_protection", '~> 1.0.1'

错误日志

web-1            | D, [2024-07-02T07:59:54.367153 #1] DEBUG -- omniauth: (linkedin) Callback phase initiated.
web-1            | OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (["access_token", "id_token"]); using "access_token".
web-1            | E, [2024-07-02T07:59:55.736180 #1] ERROR -- omniauth: (linkedin) Authentication failure! invalid_credentials: OAuth2::Error, {"status":401,"serviceErrorCode":65601,"code":"REVOKED_ACCESS_TOKEN","message":"The token used in the request has been revoked by the user"}
web-1            | Processing by OmniauthCallbacksController#failure as HTML
web-1            |   Parameters: {"code":"AQS0fi3gwHvC1Dw-VkfwyZd9wXRzPx0fQVVkyFOua4AYQsOu7MhwvwIxWTlpWFG4XExwCVYt9NOky1USrmkQrfwlWea6xETgzcJmyvkK438UM8NHGfQNkkLSPeUQi_7WgZA_O0MKQVXB01LkcfXg28QHEJ02VBNT02MYbDY_R7IhSsaCOpg2pHJfZr2yalPWt14BZhGgJNgl4YkIlUE", "state":"e5756e8c7515f899c8f3adeb3a3e504fbbcb08ff9ddc75c0"}
web-1            | Redirected to http://127.0.0.1:3000/
排查方向

从错误日志里的REVOKED_ACCESS_TOKEN和OAuth2::AccessToken.from_hash提示入手,按以下顺序排查:

  • 解决令牌解析冲突
    日志明确提示返回的哈希包含access_token和id_token两个令牌,当前oauth2 gem默认使用了access_token,但LinkedIn OpenID流程中id_token才是身份验证的关键令牌,用错令牌会触发无效错误。检查omniauth-linkedin-openid的配置,是否需要显式指定使用id_token,或重写OAuth2的令牌解析逻辑,确保正确处理双令牌结构。

  • 修复无效的重试逻辑
    当前failure方法直接调用login,但此时request.env['omniauth.auth']已不存在,重试无法获取新的授权数据。应修改为在失败时重定向到LinkedIn的授权入口,重新发起完整的授权流程,而非原地重复调用login。

  • 调整会话存储配置
    Devise配置中config.skip_session_storage = [:http_auth, :linkedin]会禁止存储LinkedIn登录相关的会话数据,可能导致回调时丢失状态信息,尤其是在分布式环境或会话过期场景。尝试移除:linkedin选项,让会话正常存储状态,观察是否减少随机失败概率。

  • 检查LinkedIn应用设置
    登录LinkedIn开发者后台,确认应用的令牌有效期、自动刷新机制是否正常;同时验证授权回调URL是否完全匹配(包括协议、域名、端口),URL不匹配可能导致令牌被标记为无效。

  • 增加详细日志定位
    取消auth_hash方法中的日志注释,记录每次授权的完整auth_hash内容,对比成功与失败请求的令牌差异;在failure方法中记录request.env['omniauth.error']的详细信息,确认是否每次失败都是同一类令牌错误,还是存在其他隐藏问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 14:33:10