背景
我的应用程序要求我连接到两个不同的 Facebook 应用程序以用于不同的目的,我正在使用下面显示的代码。
问题
当使用如下所示的方法中的自定义回调路径时,在回调处理程序中,即在我的操作中返回为零。在intridea/omniauth repo中发现了一个类似的问题。facebook_opts_for_social_sharing
ExternalApiAuthController#create_social_sharing_auth_account
request.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_path
on_callback_path?
return callback_call if on_callback_path?
方法一
为了解决这个限制,我尝试了一种发送OmniAuth::Strategies::Facebook.default 选项callback_path
的方法,
以便在每个阶段都可以接收到它。因此,我没有像在 method 中那样通过 SETUP_PROC 中的代码传递它,而是以以下方式传递它,即将它作为方法调用的选项传递:facebook_opts_for_social_sharing
OmniAuth::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 概念。试图参考网络上的一些资源来掌握它,但没有成功。
任何人都可以帮我修复我的中间件,以便它能够以所需的方式工作吗?
如果可能,请尝试让我理解@app 的概念以及@app.call(env) 应该返回的状态、标题和正文值。例如,在我的情况下,我需要中间件只有在它与所需的omniauth 路径匹配时才能继续。如果不是,它应该跳过并继续前进而不会干扰。我不知道如何实现这种行为。
PS 从过去 2 天开始,我一直在努力解决这个限制,这里提供了所有细节、我的发现、我的方法,我希望社区中的某个人一定会站出来指导我解决我的问题。
谢谢。