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

Rails Devise能否始终要求确认?如何实现定时邮件验证码2FA?

嘿,针对你提出的两个Devise相关问题,我来分享下实际开发中的思路和可行方案:

问题1:在Rails的Devise中是否始终可以要求登录确认?

Devise原生的confirmable模块核心作用是验证用户邮箱所有权,默认只在用户首次注册(或修改邮箱)时要求确认,并没有提供「每次登录都强制确认」的开关。如果想要实现类似每次登录都验证的效果,不能依赖原生confirmable的逻辑,需要自定义二次验证流程——这其实就是你第二个问题要解决的场景。

问题2:实现用户一段时间未登录后的邮箱验证码二次验证

你之前尝试修改user.confirmed_at = nil的思路确实不太合适,因为confirmed_at是Devise用来标记「邮箱是否已验证」的核心字段,修改它会混淆原生的邮箱验证逻辑,甚至触发不必要的重新确认流程。下面是一套不破坏Devise原生逻辑的实现方案:

步骤1:添加登录验证所需的数据库字段

首先给用户模型添加几个字段,用来记录登录时间和验证码信息:

# 生成迁移文件
rails generate migration AddLoginVerificationFieldsToUsers last_login_at:datetime login_verification_token:string login_verification_sent_at:datetime
# 执行迁移
rails db:migrate

步骤2:自定义Devise登录控制器,插入验证逻辑

继承Devise的SessionsController,在登录成功后判断是否需要触发二次验证:

class SessionsController < Devise::SessionsController
  def create
    # 调用Devise原生登录逻辑
    super do |user|
      # 检查用户是否需要二次验证
      if needs_login_verification?(user)
        # 生成并发送验证码
        generate_and_send_verification_token(user)
        # 先登出用户,引导到验证页面
        sign_out(user)
        redirect_to login_verification_path(uid: user.id)
        return
      else
        # 更新上次登录时间
        update_last_login_at(user)
      end
    end
  end

  private

  # 判断是否需要二次验证:已验证邮箱 + 上次登录超过2天(或从未登录)
  def needs_login_verification?(user)
    user.confirmed? && (user.last_login_at.nil? || user.last_login_at < 2.days.ago)
  end

  # 生成6位验证码并发送邮件
  def generate_and_send_verification_token(user)
    user.login_verification_token = Devise.friendly_token(6) # 生成6位随机字符串
    user.login_verification_sent_at = Time.current
    user.save(validate: false) # 跳过验证,避免不必要的字段校验
    UserMailer.login_verification(user).deliver_now
  end

  # 更新用户上次登录时间
  def update_last_login_at(user)
    user.last_login_at = Time.current
    user.save(validate: false)
  end
end

步骤3:创建验证码验证的控制器和页面

新建一个LoginVerificationsController处理验证码的验证逻辑:

class LoginVerificationsController < ApplicationController
  # 展示验证码输入页面
  def new
    @user = User.find(params[:uid])
  end

  # 处理验证码提交
  def create
    @user = User.find(params[:uid])
    # 验证验证码有效性和过期时间(15分钟内有效)
    if valid_verification_token?(@user)
      # 验证成功,清空验证码并登录
      @user.update!(login_verification_token: nil, login_verification_sent_at: nil)
      sign_in(@user)
      redirect_to root_path, notice: "登录验证成功!"
    else
      flash[:alert] = "验证码无效或已过期,请重新登录获取"
      render :new
    end
  end

  private

  def valid_verification_token?(user)
    user.login_verification_token == params[:verification_token] &&
      user.login_verification_sent_at > 15.minutes.ago
  end
end

然后创建验证码输入页面app/views/login_verifications/new.html.erb:

<h2>请输入登录验证码</h2>
<%= form_tag login_verification_path, method: :post do %>
  <%= hidden_field_tag :uid, @user.id %>
  <div>
    <%= label_tag :verification_token, "验证码" %>
    <%= text_field_tag :verification_token, nil, required: true %>
  </div>
  <div>
    <%= submit_tag "验证登录" %>
  </div>
<% end %>
<% if flash[:alert] %>
  <div class="alert alert-danger"><%= flash[:alert] %></div>
<% end %>

步骤4:配置路由

在config/routes.rb中更新Devise路由指向自定义控制器,并添加验证页面的路由:

devise_for :users, controllers: { sessions: 'sessions' }
get 'login_verification', to: 'login_verifications#new', as: :login_verification
post 'login_verification', to: 'login_verifications#create'

步骤5:创建登录验证邮件模板

生成邮件发送器:

rails generate mailer UserMailer login_verification

修改app/mailers/user_mailer.rb:

class UserMailer < ApplicationMailer
  def login_verification(user)
    @user = user
    @verification_token = user.login_verification_token
    mail(to: @user.email, subject: "你的登录验证码")
  end
end

创建邮件视图app/views/user_mailer/login_verification.html.erb:

<h1>登录验证码</h1>
<p>您好<%= @user.name %>,</p>
<p>为了你的账号安全,请输入以下验证码完成登录:</p>
<p><strong><%= @verification_token %></strong></p>
<p>验证码15分钟内有效,请勿泄露给他人。</p>

额外注意事项

  • 可以根据需求调整二次验证的时间阈值(比如把2.days.ago改成1.day.ago)
  • 验证码的长度和过期时间也可以按需修改
  • 建议给login_verification_token字段添加索引,提升查询效率

内容的提问来源于stack exchange,提问作者David Krider

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:43:24