2014-10-29 65 views
19

我很難理解並正確實施用戶身份驗證在API中。換句話說,我很難理解Grape API與前端框架(如Backbone.js,AngularJS或Ember.js)的集成。葡萄和設計的用戶身份驗證

我試圖擺動所有不同的方法,並閱讀了很多關於這個,但谷歌回報我真正不好的資源,在我看來,就像沒有真正好的文章在這個主題上 - Rails和用戶身份驗證與設計和前端框架

我會描述我目前的關鍵點,希望你能爲我的實施提供一些反饋意見,也許可以讓我指出正確的方向。

當前實現

我有後臺Rails的REST API具有以下的Gemfile(我會故意縮短所有文件代碼)

gem 'rails', '4.1.6' 
gem 'mongoid', '~> 4.0.0' 
gem 'devise' 
gem 'grape' 
gem 'rack-cors', :require => 'rack/cors' 

我目前的實施只有使用API以下路線(routes.rb):

api_base  /api  API::Base 
    GET  /:version/posts(.:format) 
    GET  /:version/posts/:id(.:format) 
    POST  /:version/posts(.:format) 
    DELETE  /:version/posts/:id(.:format) 
    POST  /:version/users/authenticate(.:format) 
    POST  /:version/users/register(.:format) 
    DELETE  /:version/users/logout(.:format) 

我創建具有以下模型user.rb

class User 
    include Mongoid::Document 
    devise :database_authenticatable, :registerable, 
     :recoverable, :rememberable, :trackable, :validatable 

    field :email,    type: String, default: "" 
    field :encrypted_password, type: String, default: "" 

    field :authentication_token, type: String 

    before_save :ensure_authentication_token! 

    def ensure_authentication_token! 
    self.authentication_token ||= generate_authentication_token 
    end 

    private 

    def generate_authentication_token 
    loop do 
     token = Devise.friendly_token 
     break token unless User.where(authentication_token: token).first 
    end 
    end 
end 

以我控制器我創建下面的文件夾結構:controllers-> API-> V1和我有創建以下共享模塊身份驗證(authentication.rb

module API 
    module V1 
    module Authentication 
     extend ActiveSupport::Concern 

     included do 
     before do 
      error!("401 Unauthorized", 401) unless authenticated? 
     end 

     helpers do 
      def warden 
      env['warden'] 
      end 

      def authenticated? 
      return true if warden.authenticated? 
      params[:access_token] && @user = User.find_by(authentication_token: params[:access_token]) 
      end 

      def current_user 
      warden.user || @user 
      end 
     end 
     end 
    end 
    end 
end 

所以每次當我想確保,我的資源將與認證令牌被調用,我可以簡單地通過調用補充一點:include API::V1::Authentication的葡萄資源:

module API 
    module V1 
    class Posts < Grape::API 
     include API::V1::Defaults 
     include API::V1::Authentication 

現在我有另一個葡萄資源稱爲Users(users.rb),在這裏我實現了用於認證,註冊和註銷的方法(我認爲我在這裏將蘋果與梨混合在一起,並且我應該將登錄/註銷過程提取到另一個Grape資源 - 會話中)。

module API 
    module V1 
    class Users < Grape::API 
     include API::V1::Defaults 

     resources :users do 
     desc "Authenticate user and return user object, access token" 
     params do 
      requires :email, :type => String, :desc => "User email" 
      requires :password, :type => String, :desc => "User password" 
     end 
     post 'authenticate' do 
      email = params[:email] 
      password = params[:password] 

      if email.nil? or password.nil? 
      error!({:error_code => 404, :error_message => "Invalid email or password."}, 401) 
      return 
      end 

      user = User.find_by(email: email.downcase) 
      if user.nil? 
       error!({:error_code => 404, :error_message => "Invalid email or password."}, 401) 
       return 
      end 

      if !user.valid_password?(password) 
       error!({:error_code => 404, :error_message => "Invalid email or password."}, 401) 
       return 
      else 
      user.ensure_authentication_token! 
      user.save 
      status(201){status: 'ok', token: user.authentication_token } 
      end 
     end 

     desc "Register user and return user object, access token" 
     params do 
      requires :first_name, :type => String, :desc => "First Name" 
      requires :last_name, :type => String, :desc => "Last Name" 
      requires :email, :type => String, :desc => "Email" 
      requires :password, :type => String, :desc => "Password" 
      end 
      post 'register' do 
      user = User.new(
       first_name: params[:first_name], 
       last_name: params[:last_name], 
       password: params[:password], 
       email:  params[:email] 
      ) 

      if user.valid? 
       user.save 
       return user 
      else 
       error!({:error_code => 404, :error_message => "Invalid email or password."}, 401) 
      end 
      end 

      desc "Logout user and return user object, access token" 
      params do 
       requires :token, :type => String, :desc => "Authenticaiton Token" 
      end 
      delete 'logout' do 

       user = User.find_by(authentication_token: params[:token]) 

       if !user.nil? 
       user.remove_authentication_token! 
       status(200) 
       { 
        status: 'ok', 
        token: user.authentication_token 
       } 
       else 
       error!({:error_code => 404, :error_message => "Invalid token."}, 401) 
       end 
      end 
     end 
    end 
    end 
end 

我意識到,我在這裏一噸的代碼,它可能沒有什麼意義,但是這是我現在有,我能夠使用authentication_token針對我的API調用它們由模塊保護Authentication

我覺得這個解決方案不好,但我真的很想找到更簡單的方法來通過API實現用戶身份驗證。我有幾個問題,我列在下面。

問題

  1. 你覺得這種實現是很危險的,如果是的話,爲什麼呢? - 我認爲這是因爲使用了一個令牌。有沒有辦法改善這種模式?我也看到了具有到期時間等的單獨模型Token的實現。但我認爲這幾乎就像重新發明輪子,因爲爲此我可以實現OAuth2。我想有更輕的解決方案。
  2. 爲身份驗證創建新模塊並將其僅包含到需要它的資源中是一種很好的做法?
  3. 你知道關於這個主題的任何好教程 - 實現 Rails + Devise + Grape嗎?另外,你是否知道開源的Rails項目有哪些好的 ?
  4. 我該如何使用更安全的不同方法來實現它?

我很抱歉這麼長的帖子,但我希望更多的人有同樣的問題,這可能會幫助我找到更多的答案在我的問題。

+0

真的,沒有人在做同樣的事情?或者其閱讀時間太長?我的天啊.... – 2014-10-29 18:01:20

回答

22

添加token_authenticable制定模塊(可與色器件版本< = 3.2)

在user.rb加:token_authenticatable制定的模塊列表,它看起來應該象下面這樣:

class User < ActiveRecord::Base 
# ..code.. 
    devise :database_authenticatable, 
    :token_authenticatable, 
    :invitable, 
    :registerable, 
    :recoverable, 
    :rememberable, 
    :trackable, 
    :validatable 

    attr_accessible :name, :email, :authentication_token 

    before_save :ensure_authentication_token 
# ..code.. 
end 

生成驗證令牌在自己的(如果色器件版本> 3.2)

class User < ActiveRecord::Base 
# ..code.. 
    devise :database_authenticatable, 
    :invitable, 
    :registerable, 
    :recoverable, 
    :rememberable, 
    :trackable, 
    :validatable 

    attr_accessible :name, :email, :authentication_token 

    before_save :ensure_authentication_token 

    def ensure_authentication_token 
    self.authentication_token ||= generate_authentication_token 
    end 

    private 

    def generate_authentication_token 
    loop do 
     token = Devise.friendly_token 
     break token unless User.where(authentication_token: token).first 
    end 
    end 

添加遷移authentiction令牌

rails g migration add_auth_token_to_users 
     invoke active_record 
     create db/migrate/20141101204628_add_auth_token_to_users.rb 

編輯遷移文件中加入:authentication_token列給用戶

class AddAuthTokenToUsers < ActiveRecord::Migration 
    def self.up 
    change_table :users do |t| 
     t.string :authentication_token 
    end 

    add_index :users, :authentication_token, :unique => true 
    end 

    def self.down 
    remove_column :users, :authentication_token 
    end 
end 

運行遷移

rake db:migrate

生成現有用戶

我們需要調用保存令牌每個確保身份驗證的用戶實例令牌存在於每個用戶。使用身份驗證令牌

您需要將下面的代碼添加到API ::根

User.all.each(&:save)

安全葡萄API中,爲了增加基於令牌的認證。如果您不知道API :: Root,請閱讀Building RESTful API using Grape

在下面的示例中,我們基於兩種方案驗證用戶 - 如果用戶登錄到Web應用程序,則使用同一會話 - 如果會話不可用和身份驗證令牌傳遞,然後找到用戶基於令牌

# lib/api/root.rb 
module API 
    class Root < Grape::API 
    prefix 'api' 
    format :json 

    rescue_from :all, :backtrace => true 
    error_formatter :json, API::ErrorFormatter 

    before do 
     error!("401 Unauthorized", 401) unless authenticated 
    end 

    helpers do 
     def warden 
     env['warden'] 
     end 

     def authenticated 
     return true if warden.authenticated? 
     params[:access_token] && @user = User.find_by_authentication_token(params[:access_token]) 
     end 

     def current_user 
     warden.user || @user 
     end 
    end 

    mount API::V1::Root 
    mount API::V2::Root 
    end 
end 
1

雖然我很喜歡這個問題,並通過@MZaragoza給出我認爲這是值得一提的是token_authentical已經從設計去除是有原因的答案!使用令牌容易受到時間攻擊。另見this postDevise's blog因此,我還沒有upvoted @ MZaragoza的答案。

如果您使用的API與看門的組合,你可以做同樣的事情,但不是檢查用戶表/模型,你看在OauthAccessTokens表中的令牌authentication_token,即

def authenticated 
    return true if warden.authenticated? 
    params[:access_token] && @user = OauthAccessToken.find_by_token(params[:access_token]).user 
end 

這更安全,因爲該令牌(即實際的access_token)只存在一段時間。

注爲了能夠做到這一點,你必須有一個用戶模式和OauthAccessToken模式,具有:

class User < ActiveRecord::Base 

    has_many :oauth_access_tokens 

end 

class OauthAccessToken < ActiveRecord::Base 
    belongs_to :user, foreign_key: 'resource_owner_id' 
end 

編輯:也請注意,一般不應該包含在URL中的access_token :http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-16#section-2.3