使用Devise通过手机号认证用户失败,求排查及解决方法
问题描述
无法通过手机号完成用户认证,需求是认证通过后返回devise-jwt令牌,但输入正确手机号后仍收到401未授权错误,日志如下:
Started POST "/api/v1/users/sign_in" for ::1 at 2023-02-02 03:06:46 +0500 Processing by Api::V1::SessionsController#create as JSON Parameters: {"users"=>{"cell"=>"4151511717"}} Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms | Allocations: 403)
相关配置代码:
routes.rb
namespace :api, defaults: { format: :json } do namespace :v1 do devise_for :users, controllers: { sessions: 'api/v1/sessions', } end end
sessions_controller.rb
class Api::V1::SessionsController < Devise::SessionsController respond_to :json private def respond_with(resource, _opts = {}) render json: { message: 'Logged.' }, status: :ok end def respond_to_on_destroy current_user ? log_out_success : log_out_failure end def log_out_success render json: { message: "Logged out." }, status: :ok end def log_out_failure render json: { message: "Logged out failure." }, status: :unauthorized end end
devise.rb
Devise.setup do |config| #............ config.secret_key = ENV['DEVISE_SECRET_KEY'] config.authentication_keys = [:cell] config.case_insensitive_keys = [:cell] config.strip_whitespace_keys = [:cell] config.jwt do |jwt| jwt.secret = ENV['DEVISE_JWT_SECRET_KEY'] jwt.expiration_time = 1.day.to_i end end
疑问:还遗漏了什么配置?是否需要重写create方法?尝试重写时发现resource为nil。
解决方案
1. 核心问题:缺少密码参数 + 参数嵌套错误
Devise默认认证逻辑需要手机号+密码组合验证,你当前请求只传了cell,且参数嵌套是复数users,但Devise期望单数user。正确请求参数格式应为:
{ "user": { "cell": "4151511717", "password": "用户密码" } }
2. 完善User模型配置
确保User模型包含devise-jwt认证策略,且有encrypted_password字段(Devise默认要求):
# app/models/user.rb class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :jwt_authenticatable, jwt_revocation_strategy: JwtDenylist # 可选:自定义JWT payload内容 def jwt_payload super.merge({ cell: self.cell }) end end
注:需先生成令牌失效管理的JwtDenylist模型,执行命令:
rails generate devise_jwt:denylist rails db:migrate
3. 完善devise-jwt配置
在devise.rb中添加令牌分发/失效的路由规则,确保sign_in请求自动生成JWT:
config.jwt do |jwt| jwt.secret = ENV['DEVISE_JWT_SECRET_KEY'] jwt.expiration_time = 1.day.to_i # 指定返回JWT的请求 jwt.dispatch_requests = [ ['POST', %r{^/api/v1/users/sign_in$}] ] # 指定失效JWT的请求 jwt.revocation_requests = [ ['DELETE', %r{^/api/v1/users/sign_out$}] ] end
4. 重写SessionsController的create方法(可选,自定义返回格式)
若需要在返回结果中明确携带JWT令牌,可重写create方法:
class Api::V1::SessionsController < Devise::SessionsController respond_to :json def create # 执行Devise认证逻辑 self.resource = warden.authenticate!(auth_options) sign_in(resource_name, resource) # 生成JWT令牌(或直接用resource.jwt_token,需模型配置正确) token = JWT.encode({ user_id: resource.id, exp: 1.day.from_now.to_i }, ENV['DEVISE_JWT_SECRET_KEY'], 'HS256') render json: { message: '登录成功', token: token }, status: :ok end # 保留原有注销逻辑 private def respond_to_on_destroy current_user ? log_out_success : log_out_failure end def log_out_success render json: { message: "注销成功" }, status: :ok end def log_out_failure render json: { message: "注销失败" }, status: :unauthorized end end
5. 重写时resource为nil的原因
你之前的请求只传了cell,未传password,Devise的warden.authenticate!方法无法通过手机号+密码的组合验证用户,因此返回nil,导致认证失败并返回401。
内容的提问来源于stack exchange,提问作者LearningROR
相关产品推荐
相关产品推荐

