使用 Grape and Devise 进行用户身份验证 [英] User Authentication with Grape and Devise
问题描述
我难以理解并正确实施 API 中的用户身份验证.换句话说,我在理解 Grape API 与 Backbone.js、AngularJS 或 Ember.js 等前端框架的集成方面存在严重问题.
I have difficulties to understand and also properly implement User Authentication in APIs. In other words, I have serious problem to understand the integration of Grape API with front-end frameworks such as Backbone.js, AngularJS or Ember.js.
我正在尝试调整所有不同的方法并阅读了很多相关内容,但是 Google 返回给我的资源非常糟糕,在我看来,好像没有关于此主题的真正好的文章 - Rails 和用户身份验证使用设计和前端框架.
I'm trying to pivot all different approaches and read a lot about that, but Google returns me truly bad resources and it seems to me, like there is no really good article on this topic - Rails and User authentication with Devise and front-end frameworks.
我将描述我当前的支点,希望您能就我的实施向我提供一些反馈,并可能为我指明正确的方向.
I will describe my current pivot and I hope you can provide me some feedback on my implementation and maybe point me to the right direction.
当前实施
我有后端 Rails REST API 和以下 Gemfile(我会故意缩短所有文件代码)
I have backend Rails REST API with following Gemfile(I will purposely shorten all file code)
gem 'rails', '4.1.6'
gem 'mongoid', '~> 4.0.0'
gem 'devise'
gem 'grape'
gem 'rack-cors', :require => 'rack/cors'
我当前的实现只有具有以下路由的 API(routes.rb):
My current implementation has only APIs with following Routes(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
I created have following model 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强>)
In my controllers I created following folder structure: controllers->api->v1 and I have created following shared module Authentication (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
添加到 Grape 资源:
So every time when I want to ensure, that my resource will be called with Authentication Token, I can simply add this by calling: include API::V1::Authentication
to the Grape resource:
module API
module V1
class Posts < Grape::API
include API::V1::Defaults
include API::V1::Authentication
现在我有另一个名为 Users(users.rb) 的 Grape 资源,在这里我实现了身份验证、注册和注销的方法.(我认为我在这里混合了苹果和梨,我应该将登录/注销过程提取到另一个葡萄资源 - 会话).
Now I have another Grape resource called Users(users.rb) and here I implement methods for authentication, registration and logout.(I think that I mix here apples with pears, and I should extract the login/logout process to another Grape resource - Session).
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
保护.
I realize that I present here a ton of code and it might not make sense, but this is what I currently have and I'm able to use the authentication_token
for calls against my API which are protected by module Authentication
.
我觉得这个解决方案不太好,但我真的在寻找更简单的方法,如何通过 API 实现用户身份验证.我有几个问题,我在下面列出.
I feel like this solution is not good, but I really looking for easier way how to achieve user authentication through APIs. I have several questions which I listed below.
问题
- 您认为这种实施是否危险,如果是,为什么?- 我认为是的,因为使用了一个令牌.有没有办法改善这种模式?我还看到了使用具有到期时间等的单独模型
Token
的实现.但我认为这几乎就像重新发明轮子,因为为此我可以实现 OAuth2.我想要更轻松的解决方案. - 创建用于身份验证的新模块并将其仅包含在需要的资源中是一种好的做法吗?
- 你知道关于这个主题的任何好的教程 - 实施导轨 + 设计 + 葡萄?另外,你知道有什么好的吗?以这种方式实现的开源 Rails 项目?
- 如何使用更安全的不同方法来实现它?
- Do you think this kind of implementation is dangerous, if so, why? - I think that it is, because of the usage of one token. Is there a way how to improve this pattern? I've also seen implementation with separate model
Token
which has expiration time, etc. But I think this is almost like reinventing wheel, because for this purpose I can implement OAuth2. I would like to have lighter solution. - It is good practice to create new module for Authentication and include it only into resources where it is needed?
- Do you know about any good tutorial on this topic - implementing Rails + Devise + Grape? Additionally, do you know about any good open-source Rails project, which is implemented this way?
- How can I implement it with different approach which is more safer?
我为这么长的帖子道歉,但我希望更多的人有同样的问题,它可以帮助我找到更多关于我的问题的答案.
I apologize for such a long post, but I hope that more people has the same problem and it might help me to find more answers on my questions.
推荐答案
添加 token_authenticable 到设计模块(适用于设计版本 <=3.2)
Add token_authenticable to devise modules (works with devise versions <=3.2)
在 user.rb 中,将 :token_authenticable 添加到设计模块列表中,它应该如下所示:
In user.rb add :token_authenticatable to the list of devise modules, it should look something like below:
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)
Generate Authentication token on your own (If devise version > 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
为身份验证令牌添加迁移
Add migration for authentiction token
rails g migration add_auth_token_to_users
invoke active_record
create db/migrate/20141101204628_add_auth_token_to_users.rb
编辑迁移文件以向用户添加 :authentication_token 列
Edit migration file to add :authentication_token column to users
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,以确保每个用户都存在身份验证令牌.
We need to call save on every instance of user that will ensure authentication token is present for each user.
User.all.each(&:save)
使用身份验证令牌保护 Grape API
Secure Grape API using auth token
您需要将以下代码添加到 API::Root 以添加基于令牌的身份验证.如果您不了解 API::Root,请阅读 使用 Grape 构建 RESTful API
You need to add below code to the API::Root in-order to add token based authentication. If you are unware of API::Root then please read Building RESTful API using Grape
在下面的示例中,我们根据两种情况对用户进行身份验证 – 如果用户登录到 Web 应用程序,则使用相同的会话 – 如果会话不可用并且传递了身份验证令牌,则根据令牌查找用户>
In below example, We are authenticating user based on two scenarios – If user is logged on to the web app then use the same session – If session is not available and auth token is passed then find user based on the token
# 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
这篇关于使用 Grape and Devise 进行用户身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!