简体   繁体   English

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

[英]Devise_token_auth and Devise login with username and email

I continuously get errors when trying to add token authentication to my existing rails application and it's stemming from the devise authentication_keys.尝试将令牌身份验证添加到我现有的 rails 应用程序时,我不断遇到错误,它源于设计 authentication_keys。 In my rails 4 app I allow users to login with either a username or email and I'd like to provide the same functionality for the API.在我的 rails 4 应用程序中,我允许用户使用用户名或电子邮件登录,我想为 API 提供相同的功能。

The error I'm getting when logging in from the API is as follows我从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:

The code for the user model is below.用户模型的代码如下。 The problem is stemming from the User load as it is not converting the login parameter to search via the email (or username).问题源于用户负载,因为它没有将登录参数转换为通过电子邮件(或用户名)进行搜索。 But the code below works totally fine for regular devise logins.但是下面的代码对于常规设备登录完全正常。

#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

Thanks for any help or guidance!感谢您的任何帮助或指导!

EDIT When sending the params as email and password there is also an error.编辑当将参数作为电子邮件和密码发送时,也会出现错误。

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. From the Devise doc , you need to do the following:设计文档,您需要执行以下操作:

    • 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 is unmaintained ( cf unmerged PR ) and therefore out-of-sync with Devise, which is why you need to override SessionsController: 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. Test example测试示例

    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

Explanation:说明:

The method find_for_database_authentication used for the authentication can be found in Devise's database_authenticable.rb file, which hosts the DatabaseAuthenticatable module.用于身份验证的find_for_database_authentication方法可以在Devise 的database_authenticable.rb文件中找到,该文件托管DatabaseAuthenticatable模块。

This is responsible for authenticating your resource while using Devise.这负责在使用 Devise 时验证您的资源。

This method is what you are overwriting in your User model, and this is why your authentication action is working fine when you use Devise .此方法是您在User模型中覆盖的方法,这就是为什么在使用Devise时您的身份验证操作可以正常工作的原因。

On the other hand, when you try to authenticate a resource with devise_token_auth , You are hitting the create action of the SessionsController < DeviseTokenAuth::ApplicationController .另一方面,当您尝试使用devise_token_auth对资源进行身份验证时,您正在点击SessionsController < DeviseTokenAuth::ApplicationControllercreate操作。

This can be seen from your error trace:这可以从您的错误跟踪中看出:

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

What this action does, as can be seen from the file here , is that it looks at the params you are sending, and builds key=>value pairs from those, and then query the database with the keys.此处的文件中可以看出,此操作的作用是查看您发送的参数,并从中构建key=>value对,然后使用键查询数据库。

So, in essence, when you send a param as:因此,本质上,当您将参数发送为:

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

Your controller will basically go into the database and look for a row with login equal email@example.com , etc..您的控制器基本上会进入数据库并查找login等于email@example.com等的行。

Problem:问题:

Unfortunately, you have no column login in your database, what you have is email , ( remember that was why you were over-riding the devise's find_for_database_authentication method in your user model =>不幸的是,您的数据库中没有列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

). )。

That is why you see the error you have in your error trace:这就是为什么您会在错误跟踪中看到错误的原因:

SQLite3::SQLException - no such column: login:

Solution:解决方法:

So, one very simple thing you can do is that when you are sending a param for authentication by the auth_token , send it as email and password因此,您可以做的一件非常简单的事情是,当您通过auth_token发送用于身份验证的参数时,将其作为emailpassword发送

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

and not as login and password而不是loginpassword

Hope this is clear.希望这很清楚。

PS: You also will not want session validation when using token, as mentioned by Shadow above, so you might want to add the following to your base controller: protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' } 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' } But this does not have to do with your reported issue. protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }但这与您报告的问题无关。

Edit:编辑:

This is the permited params method:这是允许的 params 方法:

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

So, you will have to send params inside a "sign_in" key as follow:因此,您必须在"sign_in"键中发送参数,如下所示:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM