使用Grape和Devise进行用户身份validation

我很难理解并在API中正确实现用户身份validation 。 换句话说,我很难理解Grape API与Backbone.js,AngularJS或Ember.js等前端框架的集成。

我试图调整所有不同的方法,并阅读了很多相关内容,但谷歌给我带来了真正糟糕的资源,在我看来,就像在这个主题上没有真正好的文章 – 使用Devise和前端的Rails和用户身份validation框架

我将描述我目前的支点,我希望你能就我的实施提供一些反馈,并指出我正确的方向。

目前的实施

我有跟随Gemfile的后端Rails REST API (我会故意缩短所有文件代码)

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 ,我创建了以下共享模块身份validation( 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 

因此,每次当我想确保我的资源将使用身份validation令牌调用时,我只需通过调用: include API::V1::Authentication来添加它来加入Grape资源:

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

现在我有另一个名为Users(users.rb)的Grape资源,在这里我实现了身份validation,注册和注销的方法。(我认为我在这里混合使用梨子,我应该将登录/注销过程提取到另一个Grape资源 – 会议)。

 module API module V1 class Users  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进行调用,这些API受模块Authentication保护。

我觉得这个解决方案并不好,但我真的在寻找更简单的方法来通过API实现用户身份validation。 我有几个问题,我在下面列出。

问题

  1. 你认为这种实施是危险的,如果是这样,为什么? – 我认为是因为使用了一个令牌。 有没有办法如何改善这种模式? 我也看到了具有过期时间的单独模型Token实现,等等。但我认为这几乎就像重新发明轮子,因为为此我可以实现OAuth2。 我想有更轻松的解决方案。
  2. 最好为身份validation创建新模块,并将其仅包含在需要的资源中?
  3. 你知道关于这个主题的任何好教程 – 实现Rails + Devise + Grape吗? 另外,你知道任何一个好的开源Rails项目,这是以这种方式实现的吗?
  4. 如何使用更安全的不同方法实现它?

我为这么长的post道歉,但我希望更多的人有同样的问题,这可能会帮助我找到更多关于我的问题的答案。

添加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 

自己生成身份validation令牌(如果设计版本> 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 

为真实令牌添加迁移

 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

为现有用户生成令牌

我们需要在每个用户实例上调用save,以确保每个用户都有身份validation令牌。

User.all.each(&:save)

使用身份validation令牌保护Grape API

您需要将以下代码添加到API :: Root中以添加基于令牌的身份validation。 如果您不了解API :: Root,请阅读使用Grape构建RESTful API

在下面的示例中,我们基于两种情况对用户进行身份validation - 如果用户登录到Web应用程序然后使用相同的会话 - 如果会话不可用并且传递了身份validation令牌,则根据令牌查找用户

 # 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 

虽然我喜欢@MZaragoza给出的问题和答案,但我认为值得注意的是,token_authentical已经从Devise中移除了一个原因! 使用令牌很容易受到定时攻击。 另见这篇文章和Devise的博客因为我没有赞成@ MZaragoza的答案。

如果您将您的API与Doorkeeper结合使用,您可以执行类似的操作,但不是在User表/模型中检查authentication_token,而是在OauthAccessTokens表中查找令牌,即

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

这更安全,因为该令牌(即实际的access_token)仅存在一段时间。

请注意,为了能够执行此操作,您必须具有User模型和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