簡體   English   中英

Devise_token_auth 和 Devise 使用用戶名和電子郵件登錄

[英]Devise_token_auth and Devise login with username and email

嘗試將令牌身份驗證添加到我現有的 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@example.com", "password"=>"[FILTERED]", "session"=>{"login"=>"email@example.com", "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@example.com' AND provider='email')  ORDER BY "users"."id" ASC LIMIT 1
SQLite3::SQLException: no such column: login: SELECT  "users".* FROM "users" WHERE (login = 'email@example.com' 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@example.com", "password"=>"[FILTERED]", "session"=>{"email"=>"email@example.com", "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)
  1. 設計文檔,您需要執行以下操作:

    • 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是未維護的( 參見devise_token_auth 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) && (!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?) valid_password = @resource.valid_password?(resource_params[:password]) if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_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 && !(!@resource.respond_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: "user.does.not@exist.com", 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

說明:

用於身份驗證的find_for_database_authentication方法可以在Devise 的database_authenticable.rb文件中找到,該文件托管DatabaseAuthenticatable模塊。

這負責在使用 Devise 時驗證您的資源。

此方法是您在User模型中覆蓋的方法,這就是為什么在使用Devise時您的身份驗證操作可以正常工作的原因。

另一方面,當您嘗試使用devise_token_auth對資源進行身份驗證時,您正在點擊SessionsController < DeviseTokenAuth::ApplicationControllercreate操作。

這可以從您的錯誤跟蹤中看出:

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@example.com", "password"=>"[FILTERED]"}

您的控制器基本上會進入數據庫並查找login等於email@example.com等的行。

問題:

不幸的是,您的數據庫中沒有列login ,您擁有的是email ,(請記住,這就是您在用戶模型中覆蓋設備的find_for_database_authentication方法的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發送用於身份驗證的參數時,將其作為emailpassword發送

{"email"=>"email@example.com", "password"=>"[FILTERED]"}

而不是loginpassword

希望這很清楚。

PS:在使用令牌時,您也不會需要會話驗證,如上面 Shadow 所述,因此您可能希望將以下內容添加到您的基本控制器: protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' } protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }但這與您報告的問題無關。

編輯:

這是允許的 params 方法:

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

因此,您必須在"sign_in"鍵中發送參數,如下所示:

{"sign_in" => {"email"=>"email@example.com", "password"=>"[FILTERED]"}}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM