【问题标题】:Extending Devise SessionsController to authenticate using JSON扩展 Devise SessionsController 以使用 JSON 进行身份验证
【发布时间】:2011-06-18 23:24:28
【问题描述】:

我正在尝试为 iphone 应用程序构建 Rails API。设计可以很好地通过 Web 界面登录,但我需要能够使用 REST API 创建和销毁会话,并且我想使用 JSON 而不是必须在会话控制器上执行 POST 并且必须解析 HTML 并处理重定向。

我想我可以这样做:

class Api::V1::SessionsController < Devise::SessionsController  
  def create
    super
  end  
  def destroy
    super
  end  
end

我在 config/routes.rb 中添加了:

namespace :api do
  namespace :v1 do
    resources :sessions, :only => [:create, :destroy]
  end
end

rake routes 显示路由设置正确:

   api_v1_sessions POST   /api/v1/sessions(.:format)     {:action=>"create", :controller=>"api/v1/sessions"}
    api_v1_session DELETE /api/v1/sessions/:id(.:format) {:action=>"destroy", :controller=>"api/v1/sessions"}

当我发布到 /user/sessions 时,一切正常。我得到了一些 HTML 和 302。

现在如果我发布到 /api/v1/sessions 我得到:

未知操作 AbstractController::ActionNotFound

curl -v -H 'Content-Type: application/json' -H 'Accept: application/json'   -X POST http://localhost:3000/api/v1/sessions   -d "{'user' : { 'login' : 'test', 'password' : 'foobar'}}"

【问题讨论】:

标签: ruby-on-rails ruby authentication devise


【解决方案1】:

不确定应该使用导航格式,API 不是真的...

对此blog post 只需添加

respond_to :html, :json

给你的控制器。

【讨论】:

    【解决方案2】:

    创建/销毁会话的另一种解决方案是使用 Devise 的 token_authenticatable 模块,然后更新 API 中的其他函数,以便它们将令牌作为强制参数。这可以说是一种更加 ReSTful 的设计,因为它保留了无状态(即,任何地方都没有会话状态)。当然,这个建议适用于您的 JSON API,但我不建议您的 HTML UI 使用相同的建议(浏览器 URL 栏中的长标记字符串不是很漂亮)。

    有关示例,请参阅 here

    【讨论】:

    【解决方案3】:

    我最终使用了@akshay 的答案和@mm2001 的答案的组合。

    class Api::SessionsController < Devise::SessionsController
      def create
        warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
        render :json => {:success => true}
      end
    
      def destroy
        Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
        render :json => {}
      end
    
      def failure
        render :json => {:success => false, :errors => ["Login Failed"]}
      end
    end
    

    ...在设计初始化程序中,我必须这样做才能让#create 方法使用我的:recall 处理程序

    # config/initializers/devise.rb
    config.navigational_formats = [:"*/*", "*/*", :html, :json]
    

    这适用于 Devise 1.5.1 和 Rails 3.1。

    【讨论】:

    • +1 获取config.navigational_formats 上的信息。如果您碰巧更新了设计,一件事可能会使它更加强大;您可以重写 Devise::SessionsController.auth_options 以返回您的失败方法,然后对其他所有内容使用默认实现,而不是自己实现创建和销毁(假设默认响应就足够了)。 def auth_options super.merge({:recall =&gt; "#{controller_path}#failure" }) end
    • 哦,顺便说一句,我正在使用 devise 2.0.4 和 rails 3.2。所以这可能会影响其他人是否可以做我所做的事情。
    • 这很好用,但我建议返回 401 表示失败情况。 render :json =&gt; {:success =&gt; false, :errors =&gt; ["Login Failed"]}, status: 'unauthorized'
    • @mahemoff:很好的建议!然而,地位似乎需要成为一种象征。 render :json =&gt; {:success =&gt; false, :errors =&gt; ["Login Failed"]}, status: :unauthorized
    【解决方案4】:

    我通过创建一个分配身份验证令牌的小型服务解决了这个问题。我写了一篇关于它的博客文章:http://matteomelani.wordpress.com/2011/10/17/authentication-for-mobile-devices/。 您也可以在这里获取代码:https://github.com/matteomelani/Auth-Token-Service-Prototype

    【讨论】:

    • 这种技术似乎存在一个安全漏洞,因为它绕过了可锁定模块,也许我错了......
    【解决方案5】:

    这里有一个最近的答案:http://jessehowarth.com/devise 有更多细节(根据Using Devise 1.3 to authenticate JSON login requests

    【讨论】:

      【解决方案6】:

      这终于奏效了。

      class Api::V1::SessionsController < Devise::SessionsController  
        def create  
          respond_to do |format|  
            format.html { super }  
            format.json {  
              warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")  
              render :status => 200, :json => { :error => "Success" }  
            }  
          end  
        end  
        def destroy  
          super  
        end  
      end  
      

      还要更改 routes.rb,记住顺序很重要。

      devise_for :users, :controllers => { :sessions => "api/v1/sessions" }
      devise_scope :user do
        namespace :api do
          namespace :v1 do
            resources :sessions, :only => [:create, :destroy]
          end
        end
      end
      
      resources :users
      

      【讨论】:

      • 您能详细说明一下吗?您是否在传递登录名和密码进行身份验证?
      • 补充一点:我最初在 app/views/devise/sessions/new.html.erb 下有一个自定义登录视图。我不得不将它移动到一个新的视图文件夹以匹配自定义控制器名称/命名空间。
      【解决方案7】:

      来自设计的#devise_scope 的rdoc:

      设置要在控制器中使用的设计范围。如果您有自定义路线, 您需要调用此方法(也别名为 :as)才能指定 它针对哪个控制器。

      as :user do
        get "sign_in", :to => "devise/sessions#new"
      end
      

      请注意,您不能将两个范围映射到同一个 URL。请记住,如果 您尝试在不指定范围的情况下访问设计控制器,它将 引发 ActionNotFound 错误。

      看起来您需要将其包装在 #as 块中:

      as :user do
        namespace :api do
          namespace :v1 do
            resources :sessions, :only => [:create, :destroy]
          end
        end
      end
      

      【讨论】:

      • 根据这个网址github.com/plataformatec/devise/wiki/…他的代码铺设方式是正确的。我也面临着和他一样的bug。
      • 不,不是。 rdoc 非常清楚地指出,如果您将设计控制器子类化,则需要将调用它的路由包装在 #as 或 #devise_scope 块中,否则您将收到 ActionNotFound 错误。
      猜你喜欢
      • 1970-01-01
      • 2014-02-25
      • 2011-11-20
      • 2014-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-23
      • 2014-12-24
      相关资源
      最近更新 更多