在ActiveResource中为每个请求添加API密钥。

20

我有2个RESTful Rails应用程序,正在尝试让它们相互通信。两者都是使用Rails 3(目前为beta3)编写的。对服务的请求将需要使用api密钥,这只是需要在每个请求上的参数。我似乎找不到如何实现此功能的任何信息。

您可以通过site=方法定义资源连接到的URL。应该有一个等效的query_params=方法或类似方法。

我找到了一篇与此相关的好博客文章,它发表于2008年10月,对于Rails 3并不是非常有用。

更新:我有一个想法。一个小的Rack中间件或Metal是否就是解决这个问题的答案?它可以仅传递请求,并添加其api_key。


抱歉,Nicolas,我没有找到解决方案。我认为答案是在每个请求中手动分配API密钥。 - Jared
@ Nicolas,我意识到这个问题相对较旧,但请看看我的解决方案是否符合您的要求。 - Kelvin
谢谢Kelvin。你的解决方案最接近我想要的。 - Jared
7个回答

23

使用model#prefix_options作为哈希传递参数到查询字符串中(甚至作为Model.prefix的部分替换,例如"/myresource/:param/"将被替换为prefix_options[:param]的值)。在前缀中找不到的任何哈希键都将添加到查询字符串中,在您的情况下这正是我们想要的。

class Model < ActiveResource::Base
  class << self
    attr_accessor :api_key
  end

  def save
    prefix_options[:api_key] = self.class.api_key
    super
  end
end

Model.site = 'http://yoursite/'
Model.api_key = 'xyz123'
m = Model.new(:field_1 => 'value 1')
# hits http://yoursite:80/models.xml?api_key=xyz123
m.save

现在这就是我所说的。我想你可以打开ActiveResource::Base并添加这些方法,然后它会自动发生在所有请求中。 - Jared
2
这只会修复“保存”。全局处理的唯一解决方案是由@benny-degezelle建议的那个。 - n0nick
m = Model.new m.prefix_options[:api_key] = 'xyz123' - bonyiii

10

我最近面临了一个类似的问题,如果你在使用Rails3,它支持使用自定义标头来简化这些情况。

在你发送请求的那一方,添加

headers['app_key'] = 'Your_App_Key'

到你从ActiveResource::Base继承的类中

在你的服务器上,对于身份验证,只需接收以下内容:

request.headers['HTTP_APP_KEY']

例如:

class Magic < ActiveResource::Base
    headers['app_key'] = 'Your_App_Key'
end

现在Magic.get、Magic.find和Magic.post都会发送app_key。


我使用了带有下划线 _ 的单词,但它没有起作用。当我改成连字符 - 时,它就可以了。我希望这能为某人节省一些时间。奇怪的是,我记得在开发中使用 _ 是可以的,错误发生在生产环境中。 - sites

4

我有一个更好的解决方案!我尝试使用Rack中间件,但是在这种方式中我没有找到任何解决方案...

我建议您使用此模块来覆盖ActiveResource::Base的方法

将此库添加到/lib/active_resource/extend/目录中,不要忘记取消注释config/application.rb中的"config.autoload_paths += %W(#{config.root}/lib)"

module ActiveResource #:nodoc:
  module Extend
    module AuthWithApi
      module ClassMethods
        def element_path_with_auth(*args)
          element_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
        end
        def new_element_path_with_auth(*args)
          new_element_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
        end
        def collection_path_with_auth(*args)
          collection_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
        end
      end

      def self.included(base)
        base.class_eval do
          extend ClassMethods
          class << self
            alias_method_chain :element_path, :auth
            alias_method_chain :new_element_path, :auth
            alias_method_chain :collection_path, :auth
            attr_accessor :api_key
          end
        end
      end  
    end
  end  
end

在模型中
class Post < ActiveResource::Base
  include ActiveResource::Extend::AuthWithApi

  self.site = "http://application.localhost.com:3000/"
  self.format = :json

  self.api_key = 'jCxKPj8wJJdOnQJB8ERy'

  schema do
    string  :title
    string  :content
  end

end

1
我创建了一个类,想将其用作所有资源的“基础”,但如果我将api_key放在其中,子类将无法看到它。有什么建议吗? - caarlos0

3

基于Joel Azemar的答案,但我不得不做一些改变。首先,在我使用的active resource gem(2.3.8)中,没有'new_element_path',因此给它起别名明显失败了。其次,我更新了将令牌添加到查询的方式,因为原来的方式会在您自己添加更多参数时立即中断。例如,请求http://example.com/api/v1/clients.xml?vat=0123456789?token=xEIx6fBsxy6sKLJMPVM4(注意是?token= 而非 &token=)

这是我的更新后的代码段auth_with_api.rb;

module ActiveResource #:nodoc:
  module Extend
    module AuthWithApi
      module ClassMethods
        def element_path_with_auth(id, prefix_options = {}, query_options = nil)
          query_options.merge!({:token => self.api_key})
          element_path_without_auth(id, prefix_options, query_options)
        end
        def collection_path_with_auth(prefix_options = {}, query_options = nil)
          query_options.merge!({:token => self.api_key})
          collection_path_without_auth(prefix_options, query_options)
        end
      end

      def self.included(base)
        base.class_eval do
          extend ClassMethods
          class << self
            alias_method_chain :element_path, :auth
            # alias_method_chain :new_element_path, :auth
            alias_method_chain :collection_path, :auth
            attr_accessor :api_key
          end
        end
      end  
    end
  end
end

2

我建议你创建一个继承 ActiveResource::Base 的基类,并重写 self.collection_pathself.element_path 类方法,始终注入 API KEY,例如:

class Base < ActiveResource::Base
  def self.collection_path(prefix_options = {}, query_options = {})
   super(prefix_options, query_options.merge(api_key: THE_API_KEY))
  end

  def self.element_path(id, prefix_options = {}, query_options = {})
    super(id, prefix_options, query_options.merge(api_key: THE_API_KEY))
  end
end

class User < Base; end

User.all # GET /users/?api_key=THE_API_KEY

这将始终在您的ActiveResource方法调用中注入您的API_KEY。


2

目前,Active Resource 没有一个好的方式将 API 密钥传递给远程服务。将 api_key 作为参数传递会将其添加到远程服务中对象的属性中,我想这不是您期望的行为。这当然也不是我所需要的行为。


1

一个Active Resource对象的行为很像(简化的)Active Record对象。 如果您希望通过新参数,则可以将其作为属性添加到AR对象上进行设置。例如:

jane = Person.create(:first => 'Jane', :last => 'Doe', :api_key => THE_API_KEY)

应该将api_key作为参数之一传递,与其他参数一起。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接