如何在使用API密钥时跳过Devise身份验证?

14

我在我的应用程序中使用Devise,并希望创建一个全局API密钥,可以访问任何人账户的JSON数据而无需登录。

例如,假设我的API密钥是1234,并且有两个用户分别创建了两个不同的餐厅。

  • 用户1 - 餐厅1(/restaurants/1)
  • 用户2 - 餐厅2(/restaurants/2)

如果我打开一个全新的浏览器并且没有登录,通过我的URL传入.../restaurants/2.json?api_key=1234,我应该能够访问该餐厅的JSON数据,而无需像用户2一样登录。

最佳方法是什么?

我已经按照Railscast#352 Securing an API的指示进行操作,因此我可以通过传递API密钥来访问JSON内容,但我必须先登录才能看到任何内容。

编辑1:使用CanCan

我应该提到我还在使用CanCan处理角色,但不确定它是否在这种情况下起作用。

编辑2:使用API版本控制实现

我按照 Railscast #350和#352的教程创建了REST API版本控制以及如何使用API密钥对其进行安全保护。

这是我的controllers/api/v1/restaurants/restaurants_controller.rb文件的内容:

module Api
  module V1
    class RestaurantsController < ApplicationController
      before_filter :restrict_access

      respond_to :json

      def index
        respond_with Restaurant.all
      end

      def show
        respond_with Restaurant.find(params[:id])
      end

    private

      def restrict_access
        api_key = ApiKey.find_by_access_token(params[:api_key])
        head :unauthorized unless api_key        
      end
    end
  end
end

我的application_controller.rb文件中仍然有before_filter :authenticate_user!的代码。

解决方案

我首先按照 Railscast #350 REST API版本控制 的方法,将所有JSON API调用移至/apps/api/v1/...

接着,遵循Steve Jorgensen在下方的解决方案,确保我的API模块从ActionController::Base继承,而不是从ApplicationController,这样它就可以绕过ApplicationController中Devise的before_filter :authenticate_user!代码。

所以,我的Edit 2的代码变成了这样:

module Api
  module V1
    class RestaurantsController < ApplicationController
    ...
module Api
  module V1
    #Replace 'ApplicationController' with 'ActionController::Base'
    class RestaurantsController < ActionController::Base
    ...
4个回答

17

我知道这个问题已经问了一段时间了,但是我想再提供一个过去我曾经使用的选项。

class Api::ApplicationController < ApplicationController

skip_before_filter :authenticate_user!

假设您的API目录中有一个应用程序控制器,所有api控制器都继承自该控制器。如果没有,您可以在每个控制器中放置跳过操作。


2
before_filter在Rails 5.0中已被弃用,并在Rails 5.1中删除。请使用等效的skip_before_actionbefore_action替换skip_before_filterbefore_filter。具体信息请参见https://github.com/rails/rails/blob/v5.0.0.beta2/actionpack/lib/abstract_controller/callbacks.rb#L190-L193。 - stwr667

13
你可以在控制器中使用before_filter来实现此功能。
目前,你可能有类似以下代码:
class SomeController < ApplicationController
  before_filter :authenticate_user!
end

你可以定义一个不同的方法(最好在ApplicationController中)来代替调用这个方法。

class ApplicationController < ActionController::Base
  before_filter :authenticate_or_token

  private
  def authenticate_or_token
    if params[:api_key] == 1234
      @current_user = User.new(:admin => true, :any => "other", :required => "fields")
      return current_user
    end
    authenticate_user!
  end

我建议使用更加健壮的身份验证方法,例如OAuth,但对于简单的基于1个密钥的身份验证,这应该是可行的。


感谢您的回复。但是,我按照您说的做了,但是当我尝试访问/restaurants/2.json?api_key=1234时,我收到了一个RestaurantsController#index中的NoMethodError错误,错误信息为undefined method 'has_role?' for nil:NilClass。我的ability.rb文件在第28行是这样写的:if user.has_role? :admin can :manage, :all end - FilmiHero
多么优雅的解决方案!正是我所需要的,非常感谢!@Gazler - marman

8
没有人提到的一个选项是为API完全单独设置一组控制器,这些控制器不会继承自“ApplicationController”。 我见过这样的模式,其中API控制器位于诸如“/app/controllers/api/v1/somethings.rb”之类的文件中,并且可以通过诸如“/api/v1/somethings”之类的路由访问。每个特定的API控制器都继承自一个基本的API控制器,该控制器又继承自“ActionController::Base”,因此不包括在“ApplicationController”上定义的任何过滤器。

请您能否详细说明一下?我已经跟随 Railscast #350 和 #352 学习了如何创建 API 版本控制,并通过 API 密钥进行安全保护。那么,当传递 API 密钥时,如何跳过 Devise 授权呢?目前,如果您通过 .../api/v1/restaurants?api_key=1234 传递有效的 API 密钥,我仍然需要登录。 - FilmiHero
你有一个 class RestaurantsController < ApplicationController,所以你的 API 控制器继承了 ApplicationController 的过滤器(和其他任何东西)。你需要通过直接从 ActionController::Base 继承来绕过 ApplicationController -- 或者更可能的是,从继承自 ActionController::BaseApiController 类中继承。 - Steve Jorgensen
谢谢。那个方法起作用了!我会更新我的问题并提供最终解决方案。 - FilmiHero
1
我应该注意到,尽管我在这里得到了勾选标记,但其他给出的答案也是正确的。 - Steve Jorgensen
这在当前版本的Devise中是否有效?因为它对我不起作用。 - Adt
我没有跟上最新的情况,所以我不知道。它可能有效,也可能无效,取决于当前版本的 devise。尽管如此,我仍然希望它仍然有效。你遇到了什么样的“不工作”问题? - Steve Jorgensen

5
使用 except 是 Gazler 的替代方案:
class ApplicationController < ActionController::Base
  before_filter :authenticate_user!, except: :some_json_method

  def some_json_method
    render :nothing unless params[:api_key] == '1234'

    render :json
  end
end

这样处理可以避免将整个应用程序完全暴露给密钥持有人(具体取决于您的需求,无论您是否需要这样做)。如果您需要向密钥开放多个方法,您可能还可以使用类似以下代码:
class ApplicationController < ActionController::Base
  JSON_METHODS = [method_1, method2]
  before_filter :authenticate_user!, except: JSON_METHODS
  before_filter :authenticate_token, only: JSON_METHODS

  private
  def authenticate_token
    params[:api_key] == '1234'
  end
end

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