【问题标题】:How to skip Devise authentication when using an API key?使用 API 密钥时如何跳过设计身份验证?
【发布时间】:2012-08-02 19:43:13
【问题描述】:

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

例如,假设我的 API Key 是 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 密钥对其进行保护。

这是我的控制器/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 on REST API Versioning,并将我所有的 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
    ...

【问题讨论】:

    标签: ruby-on-rails json api authentication devise


    【解决方案1】:

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

    class Api::ApplicationController < ApplicationController
    
    skip_before_filter :authenticate_user!
    

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

    【讨论】:

    【解决方案2】:

    您可以在 Controller 中使用 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 键的身份验证。

    【讨论】:

    • 感谢您的回复。但是,我照你说的做了,但我得到了NoMethodError in RestaurantsController#index. undefined method has_role?当我尝试访问/restaurants/2.json?api_key=1234 时,对于 nil:NilClass`。我在第 28 行的 ability.rb 文件是 if user.has_role? :admin can :manage, :all end
    • 多么优雅的解决方案!正是我所需要的,非常感谢! @Gazler
    【解决方案3】:

    没有人提到的一个选项是为 API 提供一组完全独立的控制器,这些控制器不继承自 ApplicationController

    我已经看到 API 控制器存在于 /app/controllers/api/v1/somethings.rb 等文件中并且可通过 /api/v1/somethings 等路由访问的模式。每个特定的 API 控制器都从继承自 ActionController::Base 的基本 API 控制器继承,因此不包括在 ApplicationController 上定义的任何过滤器。

    【讨论】:

    • 您能详细说明一下吗?我关注了 Railscast #350 和 #352,它们教您如何创建 API 版本控制并通过 API 密钥对其进行保护。那么在传入 API 密钥时如何跳过设计授权?现在,如果您通过 .../api/v1/restaurants?api_key=1234 传递有效的 API 密钥,我仍然需要登录。
    • 你有class RestaurantsController &lt; ApplicationController,所以你的API控制器从ApplicationController继承过滤器(和其他任何东西)。您需要通过直接从ActionController::Base 继承来绕过ApplicationController——或者更有可能是从从ActionController::Base 继承的ApiController 类。
    • 谢谢。那成功了!我将使用最终解决方案更新我的问题。
    • 我应该注意,虽然我在这里得到了复选标记,但这里给出的其他答案也是正确的。
    • 这对当前版本的设计也有效。因为它不适合我?
    【解决方案4】:

    Gazler 的替代方法是使用 except:

    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
    

    【讨论】:

      猜你喜欢
      • 2021-01-22
      • 1970-01-01
      • 2019-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-07
      相关资源
      最近更新 更多