You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 07:55:21