Devise_token_auth 和 Devise 使用用户名和电子邮件登录

问题描述 投票:0回答:3

当我尝试将令牌身份验证添加到现有的 Rails 应用程序时,我不断收到错误,它源于设计authentication_keys。在我的 Rails 4 应用程序中,我允许用户使用用户名或电子邮件登录,我想为 API 提供相同的功能。

我从API登录时遇到的错误如下

Started POST "/api/v1/auth/sign_in" for 127.0.0.1 at 2016-04-19 23:40:26 -0400
Processing by DeviseTokenAuth::SessionsController#create as JSON
Parameters: {"login"=>"[email protected]", "password"=>"[FILTERED]", "session"=>{"login"=>"[email protected]", "password"=>"[FILTERED]"}}
Can't verify CSRF token authenticity
Geokit is using the domain: localhost
User Load (1.3ms)  SELECT  "users".* FROM "users" WHERE (login = '[email protected]' AND provider='email')  ORDER BY "users"."id" ASC LIMIT 1
SQLite3::SQLException: no such column: login: SELECT  "users".* FROM "users" WHERE (login = '[email protected]' AND provider='email')  ORDER BY "users"."id" ASC LIMIT 1
Completed 500 Internal Server Error in 5ms

SQLite3::SQLException - no such column: login:

用户模型的代码如下。问题源于用户负载,因为它没有将登录参数转换为通过电子邮件(或用户名)进行搜索。但下面的代码对于常规设备登录来说完全可以正常工作。

#User.rb
devise :database_authenticatable, :registerable, :confirmable,
     :recoverable, :rememberable, :trackable, :validatable, :authentication_keys => [:login]
include DeviseTokenAuth::Concerns::User

def self.find_for_database_authentication(warden_conditions)
  conditions = warden_conditions.dup
  if login = conditions.delete(:login)
    where(conditions.to_h).where(['lower(username) = :value OR lower(email) = :value', { :value => login.downcase }]).first
  else
    where(conditions.to_h).first
  end
end

感谢您的帮助或指导!

编辑 当以电子邮件和密码形式发送参数时,也会出现错误。

Processing by DeviseTokenAuth::SessionsController#create as JSON
Parameters: {"email"=>"[email protected]", "password"=>"[FILTERED]", "session"=>{"email"=>"[email protected]", "password"=>"[FILTERED]"}}
Can't verify CSRF token authenticity
Unpermitted parameters: email, format, session
Completed 401 Unauthorized in 49ms (Views: 0.3ms | ActiveRecord: 0.0ms | Solr: 0.0ms)
ruby-on-rails ruby devise
3个回答
1
投票
  1. Devise 文档,您需要执行以下操作:

    • app/models/user.rb

      attr_accessor :login
      
    • config/initializers/devise.rb

      Devise.setup do |config|
         config.authentication_keys = [:login]
         config.case_insensitive_keys = [:login]
      end
      
  2. devise_token_auth
    未维护(cf 未合并 PR),因此与 Devise 不同步,这就是您需要覆盖 SessionsController 的原因:

    • config/routes.rb

      Rails.application.routes.draw do
        namespace :api do
          scope module: :v4  do
            mount_devise_token_auth_for "User", at: "auth", controllers: {
              sessions: "api/v4/devise_token_auth/sessions"
            }
          end
        end
      end
      
    • app/controllers/api/v4/devise_token_auth/sessions_controller.rb

      module Api
        module V4
          module DeviseTokenAuth
            class SessionsController < ::DeviseTokenAuth::SessionsController
              def create
                # Check
                field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
      
                @resource = nil
                if field
                  q_value = resource_params[field]
      
                  if resource_class.case_insensitive_keys.include?(field)
                    q_value.downcase!
                  end
      
                  q = "#{field.to_s} = ? AND provider='email'"
      
                  if ActiveRecord::Base.connection.adapter_name.downcase.starts_with? "mysql"
                    q = "BINARY " + q
                  end
      
                  # LOG IN BY EMAIL AND USERNAME
                  if field == :login
                    @resource = resource_class.where("email = ? OR username = ?", q_value, q_value).first
                  else
                    @resource = resource_class.where(q, q_value).first
                  end
                  # LOG IN BY EMAIL AND USERNAME END
                end
      
                if @resource && valid_params?(field, q_value) && ([email protected]_to?(:active_for_authentication?) || @resource.active_for_authentication?)
                  valid_password = @resource.valid_password?(resource_params[:password])
                  if (@resource.respond_to?(:valid_for_authentication?) && [email protected]_for_authentication? { valid_password }) || !valid_password
                    render_create_error_bad_credentials
                    return
                  end
                  # create client id
                  @client_id = SecureRandom.urlsafe_base64(nil, false)
                  @token     = SecureRandom.urlsafe_base64(nil, false)
      
                  @resource.tokens[@client_id] = {
                    token: BCrypt::Password.create(@token),
                    expiry: (Time.now + ::DeviseTokenAuth.token_lifespan).to_i
                  }
                  @resource.save
      
                  sign_in(:user, @resource, store: false, bypass: false)
      
                  yield @resource if block_given?
      
                  render_create_success
                elsif @resource && !([email protected]_to?(:active_for_authentication?) || @resource.active_for_authentication?)
                  render_create_error_not_confirmed
                else
                  render_create_error_bad_credentials
                end
              end
            end
          end
        end
      end
      
  3. 测试示例

    resource "User sign in" do
    
      let(:user) { build(:user) }
    
      before(:each) do
        user.create_new_auth_token
      end
    
      post "/api/auth/sign_in" do
        parameter :email, "Email address"
        parameter :password, "Password for user"
    
        it "fails with incorrect login and password" do
          do_request(login: "[email protected]", password: "wrong_password")
          expect(status).to eq(401)
          expect(JSON.parse(response_body)["errors"])
            .to include("Invalid login credentials. Please try again.")
        end
    
        it "logs in with correct username and password" do
          user.save!
          do_request(login: user.username, password: user.password)
          expect(status).to eq(200)
          expect(JSON.parse(response_body)["data"]["email"]).to include(user.email)
        end
    
        it "logs in with correct email and password" do
          user.save!
          do_request(login: user.email, password: user.password)
          expect(status).to eq(200)
          expect(JSON.parse(response_body)["data"]["email"]).to include(user.email)
        end
      end
    end
    

0
投票

当我使用 devise-token-auth gem 时,只有一个班轮代码对我有用

Devise.setup do |config|
  config.authentication_keys = [:email, :username]
end

-1
投票

说明:

用于身份验证的方法

find_for_database_authentication
可以在 Devise 的
database_authenticable.rb
文件中找到,该文件托管
DatabaseAuthenticatable
模块。

这负责在使用 Devise 时验证您的资源。

此方法是您在

User
模型中覆盖的方法,这就是为什么当您使用 Devise 时,您的身份验证操作可以正常工作。

另一方面,当您尝试使用 devise_token_auth 验证资源时,您将点击

create
SessionsController < DeviseTokenAuth::ApplicationController
操作。

这可以从您的错误跟踪中看出:

Started POST "/api/v1/auth/sign_in" for 127.0.0.1 at 2016-04-19 23:40:26 -0400
Processing by DeviseTokenAuth::SessionsController#create as JSON

此处的文件可以看出,此操作的作用是查看您发送的参数,并根据这些参数构建

key=>value
对,然后使用键查询数据库。

因此,本质上,当您发送参数时:

{"login"=>"[email protected]", "password"=>"[FILTERED]"}

您的控制器基本上会进入数据库并查找

login
等于
[email protected]
的行,等等..

问题:

不幸的是,你的数据库中没有列

login
,你拥有的是
email
,(记住这就是为什么你在用户模型中覆盖了设备的
find_for_database_authentication
方法=>

where(conditions.to_h).where(['lower(username) = :value OR lower(email) = :value', { :value => login.downcase }]).first

)。

这就是您在错误跟踪中看到错误的原因:

SQLite3::SQLException - no such column: login:

解决方案:

因此,您可以做的一件非常简单的事情是,当您通过 auth_token 发送用于身份验证的参数时,将其作为

email
password

发送
{"email"=>"[email protected]", "password"=>"[FILTERED]"}

而不是

login
password

希望这是清楚的。

PS:使用令牌时您也不需要会话验证,正如上面 Shadow 所提到的,因此您可能需要将以下内容添加到基本控制器中:

protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
但这与您报告的问题无关。

编辑:

这是允许的参数方法:

def resource_params
  params.permit(*params_for_resource(:sign_in))
end

因此,您必须在

"sign_in"
键内发送参数,如下所示:

{"sign_in" => {"email"=>"[email protected]", "password"=>"[FILTERED]"}}
© www.soinside.com 2019 - 2024. All rights reserved.