问题描述
我很难理解,并在API中正确实施 用户认证 。换句话说,我有严重的问题要了解Grape API与Frontbone.js,AngularJS或Ember.js等前端框架的集成。
我正在设法解决所有不同的方法,并阅读了很多关于这一点的信息,但是Google返回了我真正的坏资源,在我看来,就像没有真正的好关于这个主题的文章 - 使用Devise和前端框架的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( route.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,:可注册,
:可恢复,可记忆,可追踪,可验证
字段:电子邮件,类型:字符串,默认值:
字段:encrypted_password ,键入:String,默认值:
字段:authentication_token,键入: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除非User.where(authentication_token:token).first
end
end
end
在我的控制器中,我创建了以下文件夹结构: controllers-> api-> v1 ,并且我创建了以下共享模块验证( 认证.rb )
模块API
模块V1
模块验证
扩展ActiveSupport :: Concern
包括do
在做
错误!(401未经授权,401)除非经过身份验证?
end
helpers do
def warden
env ['warden']
end
def authenticated?
如果warden.authenticated返回true?
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
所以每次当我想要确保我的资源将被使用身份验证令牌调用时,我可以通过调用:$ code> include API :: V1: :认证到Grape资源:
模块API
模块V1
类帖子葡萄:: API
包含API :: V1 ::默认值
包含API :: V1 ::验证
现在我有另一个Grape资源,名为Users(users.rb),这里我实现了身份验证,注册和注销的方法(我认为我将梨与梨混合在一起,我应该提取登录/注销过程到另一个Grape资源 - 会话)。
模块API
模块V1
类用户& Grape :: API
包含API :: V1 ::默认值
资源:用户执行
desc验证用户并返回用户对象,访问令牌
params do
要求:email,:type => String,:desc => 用户电子邮件
要求:password,:type => String,:desc => 用户密码
end
post'authenticate'do
email = params [:email]
password = params [:password]
如果电子邮件。零?或password.nil?
错误!({:error_code => 404,:error_message =>电子邮件或密码无效),401)
返回
结束
用户= User.find_by(email:email.downcase)
如果user.nil?
错误!({:error_code => 404,:error_message =>电子邮件或密码无效),401)
return
end
if !user.valid_password?(密码)
错误!({:error_code => 404,:error_message =>无效的电子邮件或密码),401)
return
else b $ b user.ensure_authentication_token!
user.save
status(201){status:'ok',token:user.authentication_token}
end
end
desc注册用户并返回用户对象,访问令牌
params do
require:first_name,:type => String,:desc => 名字
要求:last_name,:type => String,:desc => 姓氏
要求:email,:type => String,:desc => 电子邮件
要求:password,:type => String,:desc => 密码
end
post'register'do
user = User.new(
first_name:params [:first_name],
last_name:params [:last_name] ,
密码:params [:password],
电子邮件:params [:email]
)
如果user.valid?
user.save
return user
else
error!({:error_code => 404,:error_message =>无效的电子邮件或密码。 b $ b end
end
desc退出用户并返回用户对象,访问令牌
params do
require:token,:type => String,:desc => Authenticaiton Token
end
delete'logout'do
user = User.find_by(authentication_token:params [:token])
如果! user.nil?
user.remove_authentication_token!
状态(200)
{
状态:'ok',
令牌:user.authentication_token
}
else
error!({ :error_code => 404,:error_message =>无效的令牌。},401)
end
end
end
end
end
结束
我意识到我在这里提供了大量的代码,可能没有意义,但这是我现在拥有的,我可以使用 authentication_token
对我的API进行调用,这些调用受到认证
我觉得这个解决方案不好,但是我真的很想找到如何通过API实现用户身份验证。我有几个问题,我在下面列出。
问题
- 你认为这种实现是否危险,如果是这样,为什么? - 我认为是因为使用了一个令牌。有没有办法改善这种模式?我也看到实现与单独的模型
令牌
有过期时间等等。但我认为这几乎就像是重新发明轮,因为为此目的我可以实现OAuth2。我想要更轻的解决方案。 - 为验证创建新模块是很好的做法,仅将其包含在需要的资源中。
- 你知道关于这个主题的任何好的教程 - 实现
Rails + Devise + Grape?另外,你知道任何一个好的
开源的Rails项目,这是通过这种方式实现的? - 如何使用更安全的不同方法来实现? li>
对于这么长的帖子,我很抱歉,但我希望更多的人有同样的问题,这可能会帮助我找到更多的答案
添加token_authenticable以设计模块(使用devise版本< = 3.2)
在user.rb中添加:token_authenticatable到设计模块列表,它应该如下所示:
class User< ActiveRecord :: Base
#..code ..
devise:database_authenticatable,
:token_authenticatable,
:invitable,
:可注册,
:可恢复,
:可记忆,
:可追踪,
:可验证
attr_accessible:name,:email,,authentication_token
before_save:ensure_authentication_token
#..code ..
end
自己生成身份验证令牌(如果设计版本> 3.2)
class User< ActiveRecord :: Base
#..code ..
devise:database_authenticatable,
:invitable,
:可注册,
:可恢复,
:可记忆,
:可追踪,
:可验证
attr_accessible:name,:email,,authentication_token
before_save:ensure_authentication_token
def确保_authentication_token
self.authentication_token || = generate_authentication_token
end
private
def generate_authentication_token
loop do
token = Devise。 friendly_token
break token除非User.where(authentication_token:token).first
end
end
为验证令牌添加迁移
rails g migration add_auth_token_to_users
invoke active_record
创建db / migrate / 20141101204628_add_auth_token_to_users.rb
编辑迁移文件以添加:authenti对用户的阳离子列为
class AddAuthTokenToUsers< ActiveRecord :: Migration
/ pre>
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
为现有用户生成令牌
我们需要在每个确保身份验证的用户实例上调用保存
User.all.each(&:save)
使用认证令牌安全的Grape API
您需要添加以下代码到API :: Root,以添加令牌基于身份验证。如果您不知道API :: Root,请阅读
在下面的示例中,我们基于两种情况验证用户 - 如果用户登录到Web应用程序,则使用相同的会话 - 如果会话不可用,并且auth令牌被传递,则基于令牌
#lib / api / root查找用户。 rb
模块API
class Root<葡萄:: API
前缀'api'
格式:json
rescue_from:all,,backtrace => true
error_formatter:json,API :: ErrorFormatter
before do
error!(401 Unauthorized,401)除非经过验证
end
helpers做
def warden
env ['warden']
end
def authenticated
如果warden.authenticated返回true?
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
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.
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.
Current implementation
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'
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)
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
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
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
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
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 moduleAuthentication
.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.
Questions
- 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 - implementingRails + Devise + Grape? Additionally, do you know about any goodopen-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.
解决方案Add token_authenticable to devise modules (works with devise versions <=3.2)
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
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
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
Run migrations
rake db:migrate
Generate token for existing users
We need to call save on every instance of user that will ensure authentication token is present for each user.
User.all.each(&:save)
Secure Grape API using auth token
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
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
这篇关于葡萄和设计用户认证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!