使用 Grape and Devise 进行用户身份验证 [英] User Authentication with Grape and Devise

查看:46
本文介绍了使用 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.

问题

  1. 您认为这种实施是否危险,如果是,为什么?- 我认为是的,因为使用了一个令牌.有没有办法改善这种模式?我还看到了使用具有到期时间等的单独模型 Token 的实现.但我认为这几乎就像重新发明轮子,因为为此我可以实现 OAuth2.我想要更轻松的解决方案.
  2. 创建用于身份验证的新模块并将其仅包含在需要的资源中是一种好的做法吗?
  3. 你知道关于这个主题的任何好的教程 - 实施导轨 + 设计 + 葡萄?另外,你知道有什么好的吗?以这种方式实现的开源 Rails 项目?
  4. 如何使用更安全的不同方法来实现它?
  1. 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.
  2. It is good practice to create new module for Authentication and include it only into resources where it is needed?
  3. 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?
  4. 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屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆