User Authentication with Grape and Devise
我很难理解并且也无法正确实现API中的用户身份验证。换句话说,我很难理解Grape API与前端框架(例如Backbone.js,AngularJS或Ember.js)的集成。
我正在尝试探讨所有不同的方法,并阅读了很多相关内容,但是Google向我退回的资源确实很差,在我看来,好像没有关于该主题的非常好的文章-使用Devise进行Rails和用户身份验证和前端框架。
我将描述我目前的工作重点,希望您能向我提供有关实施情况的反馈,也许会指出正确的方向。
当前实现
我具有带有以下Gemfile的后端Rails REST API(我将特意缩短所有文件代码)
1 2 3 4 5 | gem 'rails', '4.1.6' gem 'mongoid', '~> 4.0.0' gem 'devise' gem 'grape' gem 'rack-cors', :require => 'rack/cors' |
我当前的实现仅具有以下Routes(routes.rb)的API:
1 2 3 4 5 6 7 8 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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 |
因此,每当我要确保使用身份验证令牌来调用我的资源时,我只需将以下内容添加到葡萄资源中即可:
1 2 3 4 5 | module API module V1 class Posts < Grape::API include API::V1::Defaults include API::V1::Authentication |
现在我有另一个名为Users(users.rb)的Grape资源,在这里我实现了身份验证,注册和注销的方法。(我认为我在这里将苹果和梨混合在一起,应该将登录/注销过程提取到另一个葡萄资源-会话)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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 |
我意识到我在这里展示了大量代码,这可能没有意义,但这是我目前拥有的,并且我能够使用
我觉得这个解决方案不好,但是我确实在寻找更简单的方法来通过API实现用户身份验证。我有以下几个问题。
问题
Rails Devise Grape?此外,您是否知道任何好处
开源的Rails项目,以这种方式实现?
对于这么长的帖子,我深表歉意,但我希望更多的人遇到同样的问题,这可能有助于我找到更多关于我的问题的答案。
添加token_authenticable来设计模块(适用于设计版本<= 3.2)
在user.rb中将:token_authenticatable添加到devise模块列表中,其外观应类似于以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 |
为身份验证令牌添加迁移
1 2 3 | rails g migration add_auth_token_to_users invoke active_record create db/migrate/20141101204628_add_auth_token_to_users.rb |
编辑迁移文件以向用户添加:authentication_token列
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
运行迁移
为现有用户生成令牌
我们需要在每个用户实例上调用save,以确保为每个用户提供身份验证令牌。
使用身份验证令牌保护葡萄API
您需要按以下顺序向API :: Root添加以下代码,以添加基于令牌的身份验证。如果您不知道API :: Root,请阅读使用Grape
构建RESTful API。
在下面的示例中,我们基于两种情况对用户进行身份验证–如果用户登录到Web应用程序,则使用同一会话–如果会话不可用,并且传递了身份验证令牌,则根据令牌 尽管我喜欢@MZaragoza给出的问题和答案,但我认为值得注意的是,token_authentical已从Devise中删除是有原因的!令牌的使用容易受到定时攻击。另请参阅此帖子和Devise的博客,因此我尚未对@MZaragoza的答案进行投票。 如果您将API与Doorkeeper结合使用,则可以执行类似的操作,但是您无需在User表/模型中检查authentication_token,而是在OauthAccessTokens表中查找令牌,即 这更安全,因为该令牌(即实际的access_token)仅存在一定的时间。 请注意,为了能够执行此操作,您必须具有User模型和OauthAccessToken模型,并具有: 编辑:也请注意,通常您不应在URL中包含access_token:http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-16#section-2.3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
2
3
4
return true if warden.authenticated?
params[:access_token] && @user = OauthAccessToken.find_by_token(params[:access_token]).user
end
2
3
4
5
6
7
8
9
has_many :oauth_access_tokens
end
class OauthAccessToken < ActiveRecord::Base
belongs_to :user, foreign_key: 'resource_owner_id'
end