1

背景

我的应用程序要求我连接到两个不同的 Facebook 应用程序以用于不同的目的,我正在使用下面显示的代码。

问题

当使用如下所示的方法中的自定义回调路径时,在回调处理程序中,即在我的操作中返回为零。在intridea/omniauth repo中发现了一个类似的问题。facebook_opts_for_social_sharingExternalApiAuthController#create_social_sharing_auth_accountrequest.env['omniauth.auth']

/config/routes.rb

  get '/auth/:provider/callback', to: 'external_api_auth#create'
  get '/auth/:provider/social_sharing/callback', to: 'external_api_auth#create_social_media_platform_account', as: :social_sharing_auth
  get '/auth/failure', to: 'external_api_auth#failure'

/app/controllers/external_api_auth_controller.rb

  class ExternalApiAuthController
    # GET /auth/failure
    def failure
    end

    # GET /auth/:provider/callback
    def create
    end

    # GET /auth/:provider/social_sharing/callback
    def create_social_media_platform_account
    end
  end

/config/initializers/omniauth.rb

    def provider_facebook
      'facebook'
    end

    def facebook_opts
      my_model_obj = MyModelService.find_by_provider_name(provider_facebook)

      return unless my_model_obj.present?

      app_details_hash = my_model_obj.application_details
      client_id = app_details_hash[:client_id]
      client_secret = app_details_hash[:client_secret]

      return if client_id.blank? || client_secret.blank?

      {
        client_id: client_id,
        client_secret: client_secret,
        scope: 'email,manage_pages,publish_pages',
        display: 'popup'
      }
    end

    def facebook_opts_for_social_sharing
      my_model_obj = MyAnotherModelService.find_by_internal_name(provider_facebook)

      return unless my_model_obj.present?

      app_details_hash = my_model_obj.application_details
      client_id = app_details_hash[:client_id]
      client_secret = app_details_hash[:client_secret]

      return if client_id.blank? || client_secret.blank?

      {
        client_id: client_id,
        client_secret: client_secret,
        scope: 'email,manage_pages,publish_pages',
        display: 'popup',
        callback_path: ExternalApiAuthUrl.sharing_auth_callback_path(provider: provider_facebook)
      }
    end

    SETUP_PROC = lambda do |env|
      strategy_instance = env['omniauth.strategy']
      provider_name = provider_name_from_oauth_strategy_class(strategy_instance.class)

      request = Rack::Request.new(env)

      is_social_sharing_auth = false

      auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
      if ExternalApiAuthUrl.is_auth_purpose_sharing?(auth_purpose: auth_purpose)
        is_social_sharing_auth = true
      end

      opts = case provider_name.downcase.underscore
              when 'facebook'
                ( is_social_sharing_auth ? facebook_opts_for_sharing : facebook_opts )
              else
                nil
             end

      if opts.present?
        env['omniauth.strategy'].options.merge!(opts)
      end
    end

    OmniAuth.config.logger = Rails.logger

    OmniAuth.config.on_failure do |env|
      .....
      .....
    end

    Rails.application.config.middleware.use OmniAuth::Builder do
      # Reference: https://github.com/intridea/omniauth/wiki/Setup-Phase
      provider :facebook, setup: SETUP_PROC
    #end

使用该代码,正在发生的事情是在Request Phasecallback_path期间正确获取。然而,一旦请求阶段完成并且OmniAuth::Strategies::OAuth2#request_phase 启动重定向,OmniAuth::Strategy实例仅 使用OmniAuth::Strategies::Facebook.default 选项。由于这些选项不包含(启动重定向之后),而评估以下行 总是返回 false,因此回调阶段永远没有机会执行。callback_pathon_callback_path?return callback_call if on_callback_path?

方法一

为了解决这个限制,我尝试了一种发送OmniAuth::Strategies::Facebook.default 选项callback_path的方法, 以便在每个阶段都可以接收到它。因此,我没有像在 method 中那样通过 SETUP_PROC 中的代码传递它,而是以以下方式传递它,即将它作为方法调用的选项传递:facebook_opts_for_social_sharingOmniAuth::Builder#provider

Rails.application.config.middleware.use OmniAuth::Builder do provider :facebook, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.sharing_auth_callback_path(provider: provider_facebook) end

并使其工作更新 SETUP_PROC 看起来像

    SETUP_PROC = lambda do |env|
      strategy_instance = env['omniauth.strategy']
      provider_name = provider_name_from_oauth_strategy_class(strategy_instance.class)

      request = Rack::Request.new(env)

      is_social_sharing_auth = false

      auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
      if ExternalApiAuthUrl.is_auth_purpose_sharing?(auth_purpose: auth_purpose)
        is_social_sharing_auth = true
      elsif ( request.path_info.casecmp(ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name)) == 0 )
        is_social_sharing_auth = true
      end

      opts = case provider_name.downcase.underscore
              when 'facebook'
                ( is_social_sharing_auth ? facebook_opts_for_sharing : facebook_opts )
              else
                nil
             end

      unless is_social_sharing_auth
        env['omniauth.strategy'].options.delete(:callback_path)
      end

      if opts.present?
        env['omniauth.strategy'].options.merge!(opts)
      end
    end

然而,这使得自定义 callback_path场景工作,但默认 callback_path /auth/facebook/callback场景失败,因为包含自定义 callback_path 的选项在OmniAuth::Strategy实例callback_path中始终可用。

方法二

因此,为了解决方法 1带来的限制,我尝试了另一种使用中间件的方法,该中间件基于请求的 path_info 和 params 调用带有所需选项的策略中间件。

/app/middleware/omniauth_builder_setup.rb

    class OmniauthBuilderSetup
      def initialize(app)
       @app = app
      end

      def call(env)
        request = Rack::Request.new(env)

        Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup @app: #{@app.inspect}"

        provider_name = provider_name(request.path_info)

        unless provider_name
          status, headers, response = @app.call(env)
          return [status, headers, response]
        end

        is_social_sharing_auth = false

        auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
        if ExternalApiAuthUrl.is_auth_purpose_reviews_social_sharing?(auth_purpose: auth_purpose)
          is_social_sharing_auth = true
        elsif ( request.path_info.casecmp(ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name)) == 0 )
          is_social_sharing_auth = true
        end

        if is_social_sharing_auth
          middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name))
        else
          middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC)
        end

        Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #{middleware_instance.inspect}"

        @app = middleware_instance

        status, headers, response = @app.call(env)

        [status, headers, response]
      end

      private

      def provider_name_regex
        # matches
        #  /auth/facebook
        #  /auth/facebook/callback
        #  /auth/facebook?auth_purpose=social_sharing

        /\A\/auth\/(facebook|twitter)(?:((\/.*)|(\?.+=.+))?)\z/
      end

      def provider_name(path_info)
        match_data = path_info.match(provider_name_regex)

        return if match_data.nil?

        match_data.captures.first
      end

      def omniauth_strategy_middleware(klass, *args, &block)
        if klass.is_a?(Class)
          middleware = klass
        else
          begin
            middleware = OmniAuth::Strategies.const_get("#{OmniAuth::Utils.camelize(klass.to_s)}")
          rescue NameError
            raise(LoadError.new("Could not find matching strategy for #{klass.inspect}. You may need to install an additional gem (such as omniauth-#{klass})."))
          end
        end

        args.last.is_a?(Hash) ? args.push({}.merge(args.pop)) : args.push({})
        middleware.new(middleware, *args, &block)
      end
    end

/config/application.rb

  ....

    config.middleware.use "OmniauthBuilderSetup"
  ....

/config/initializers/omniauth.rb(注释掉use OmniAuth::Builder

  ....
  ......
  .....

  #Rails.application.config.middleware.use OmniAuth::Builder do
  #  provider :facebook, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.reviews_social_sharing_auth_callback_path(provider: provider_facebook)
  #end

使用这种中间件方法,回调阶段在两种情况下都会启动,即使用默认 callback_path/auth/facebook/callback和自定义 callback_path时/auth/facebook/social_sharing/callback。但在回调阶段但失败并出现以下错误:

      undefined method `call' for OmniAuth::Strategies::Facebook:Class Did you mean? caller

我在OmniAuth::Strategy中添加了一些日志语句, 并生成了以下日志。

      Started GET "/auth/facebook" for 127.0.0.1 at 2016-07-28 10:28:23 +0530
      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<ActionDispatch::Routing::RouteSet:0x000000073a64c8>
      >>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
      (facebook) Setup endpoint detected, running now.
      (facebook) Request phase initiated.


      Started GET "/auth/facebook/callback?code=AQDxel76u_UvtTeSHUw3CzMpA98KTI4V_75qhxV5TGD7rdGcyeCX-FS1nrrlo-EAezZXUPdH9cAC5h4c1xlqoIL7UZ2WLDfXHG4GHWZTEGYHzH7QURNSkrjvDoBNWV90E83f_R6RURl1POsq8ZhmQOFD5YGXRxosiVx4Sof8_vqJZ5UT2S5SFbmVLEtaZZacJDqEbWjNKBrYdrZauuqCS91lEw6Lrz5U5rA2eOmmygAiBwso-cnmOuRu-PptwtIbBL5zw5hPOANQskIFHL-lfbobZYBwy_NsY8Nf-HsJauuymSmtfsQ28UaPlkox9vSinqDAHYhW1ltBXrOX_7P4HfBr&state=3831c127892242fb43aaa2ebfe37cac9e0cd2c8dbea06f3e" for 127.0.0.1 at 2016-07-28 10:28:29 +0530
      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniAuth::Strategy call!(env) @app OmniAuth::Strategies::Facebook
      >>>>>>>>>>>>> OmniAuth::Strategy call!(env) options #<OmniAuth::Strategy::Options access_token_options=#<OmniAuth::Strategy::Options header_format="OAuth %s" param_name="access_token"> auth_token_params=#<OmniAuth::Strategy::Options> authorize_options=[:scope, :display, :auth_type] authorize_params=#<OmniAuth::Strategy::Options> client_id=nil client_options=#<OmniAuth::Strategy::Options authorize_url="https://www.facebook.com/dialog/oauth" site="https://graph.facebook.com" token_url="oauth/access_token"> client_secret=nil name="facebook" provider_ignores_state=false setup=#<Proc:0x000000065ead70@/jwork/ruby/ror_projects/Reviewgo-JonathanSmith/reviewgo/config/initializers/omniauth.rb:76 (lambda)> skip_info=false token_options=[] token_params=#<OmniAuth::Strategy::Options parse=:query>>
      >>>>>>>>>>>>> OmniAuth::Strategy call!(env) class: OmniAuth::Strategies::Facebook
      >>>>>>>>>>>>>>OmniAuth::Strategy call!(env) current_path: /auth/facebook/callback
      >>>>>>>>>>>>>>OmniAuth::Strategy call!(env) on_callback_path?: true
      (facebook) Setup endpoint detected, running now.
      (facebook) Callback phase initiated.

      NoMethodError (undefined method `call' for OmniAuth::Strategies::Facebook:Class
      Did you mean?  caller):
        app/middleware/omniauth_builder_setup.rb:61:in `call'

如果您在我的中间件的回调阶段注意到 @app 拥有一个 OmniAuth::Strategies::Facebook 的实例,但是一旦控件到达 OmniAuth::Strategy 中的 OmniAuth::Strategy @app 实例就引用了 class OmniAuth::Strategies::Facebook

      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniauthBuilderSetup @app: #<OmniAuth::Strategies::Facebook>
      >>>>>>>>>>>>> OmniAuth::Strategy call!(env) @app OmniAuth::Strategies::Facebook

我确信我的中间件有问题。我以前没有使用过中间件,所以不理解这个@app 概念。试图参考网络上的一些资源来掌握它,但没有成功。

  1. 任何人都可以帮我修复我的中间件,以便它能够以所需的方式工作吗?

  2. 如果可能,请尝试让我理解@app 的概念以及@app.call(env) 应该返回的状态、标题和正文值。例如,在我的情况下,我需要中间件只有在它与所需的omniauth 路径匹配时才能继续。如果不是,它应该跳过并继续前进而不会干扰。我不知道如何实现这种行为。

PS 从过去 2 天开始,我一直在努力解决这个限制,这里提供了所有细节、我的发现、我的方法,我希望社区中的某个人一定会站出来指导我解决我的问题。

谢谢。

4

2 回答 2

1

呜呜呜呜终于解决了。我在我的中间件中解决了这个问题,它开始工作了。感谢这篇文章及其接受的答案,它指导我在中间件中找到问题并修复它。

需要在以下方法中更改代码。下面显示的是以前的版本,如我上面的帖子所示:

  def call(env)
    request = Rack::Request.new(env)

    Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup @app: #{@app.inspect}"

    provider_name = provider_name(request.path_info)

    unless provider_name
      status, headers, response = @app.call(env)
      return [status, headers, response]
    end

    is_social_sharing_auth = false

    auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
    if ExternalApiAuthUrl.is_auth_purpose_reviews_social_sharing?(auth_purpose: auth_purpose)
      is_social_sharing_auth = true
    elsif ( request.path_info.casecmp(ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name)) == 0 )
      is_social_sharing_auth = true
    end

    if is_social_sharing_auth
      middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name))
    else
      middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC)
    end

    Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #{middleware_instance.inspect}"

    @app = middleware_instance

    status, headers, response = @app.call(env)

    [status, headers, response]
  end


  def omniauth_strategy_middleware(klass, *args, &block)
    if klass.is_a?(Class)
      middleware = klass
    else
      begin
        middleware = OmniAuth::Strategies.const_get("#{OmniAuth::Utils.camelize(klass.to_s)}")
      rescue NameError
        raise(LoadError.new("Could not find matching strategy for #{klass.inspect}. You may need to install an additional gem (such as omniauth-#{klass})."))
      end
    end

    args.last.is_a?(Hash) ? args.push({}.merge(args.pop)) : args.push({})
    middleware.new(middleware, *args, &block)
  end

修改后的代码:

  def call(env)
    request = Rack::Request.new(env)

    Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup @app: #{@app.inspect}"

    provider_name = provider_name(request.path_info)

    unless provider_name
      status, headers, response = @app.call(env)
      return [status, headers, response]
    end

    is_social_sharing_auth = false

    auth_purpose = request.params[ExternalApiAuthUrl::AUTH_PURPOSE_PARAM_NAME]
    if ExternalApiAuthUrl.is_auth_purpose_reviews_social_sharing?(auth_purpose: auth_purpose)
      is_social_sharing_auth = true
    elsif ( request.path_info.casecmp(ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name)) == 0 )
      is_social_sharing_auth = true
    end

    if is_social_sharing_auth
      middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC, callback_path: ExternalApiAuthUrl.social_sharing_auth_callback_path(provider: provider_name))
    else
      middleware_instance = omniauth_strategy_middleware(provider_name, setup: SETUP_PROC)
    end

    Rails.logger.debug ">>>>>>>>>>>>> OmniauthBuilderSetup middleware_instance: #{middleware_instance.inspect}"

    status, headers, response = middleware_instance.call(env) # <<<<<--------- Changed here

    [status, headers, response]
  end


  def omniauth_strategy_middleware(klass, *args, &block)
    if klass.is_a?(Class)
      middleware = klass
    else
      begin
        middleware = OmniAuth::Strategies.const_get("#{OmniAuth::Utils.camelize(klass.to_s)}")
      rescue NameError
        raise(LoadError.new("Could not find matching strategy for #{klass.inspect}. You may need to install an additional gem (such as omniauth-#{klass})."))
      end
    end

    args.last.is_a?(Hash) ? args.push({}.merge(args.pop)) : args.push({})
    middleware.new(@app, *args, &block) # <<<<<--------- Changed here
  end

更新:在https://gist.github.com/jiggneshhgohel/4a79aa26cb628533fe132295cffc45b2创建了一个包含整个代码的要点。希望它可以帮助像我这样的人并且可以节省时间。

谢谢。

于 2016-07-28T07:01:48.970 回答
0

遇到这个问题并在这里发布了一个更简单的解决方法:https ://github.com/omniauth/omniauth/issues/661#issuecomment-630300394

基本上,只需使用查询字符串参数,它就会传回给您。不确定是否所有提供商都会这样做,但它对我有用,所以可能值得一试。

于 2020-05-18T16:43:40.400 回答