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

如何为Active Admin自定义会话控制器并添加双因素认证路由?

解决Active Admin自定义会话控制器无法添加双因素认证路由的问题

我之前在给Active Admin集成双因素认证时,也碰到过完全相同的路由问题——直接添加GET路由根本不生效,后来才搞清楚是Active Admin的Devise路由机制的特殊性导致的。下面是我验证过的可行解决方案:

1. 正确配置自定义路由(核心步骤)

Active Admin通过devise_for生成的路由优先级很高,直接添加的独立路由会被覆盖或者无法正确匹配到我们重写的控制器。解决办法是用devise_scope :admin_user来包裹所有自定义的验证路由,这样能明确关联到AdminUser的Devise作用域。

修改你的config/routes.rb:

devise_for :admin_users, ActiveAdmin::Devise.config

# 用devise_scope包裹自定义验证路由
devise_scope :admin_user do
  # 选择验证方式的GET路由
  get '/admin/verification_method', to: 'active_admin/devise/sessions#verification_method', as: :verification_method
  # 后续OTP相关路由也加在这里
  post '/admin/verify_otp', to: 'active_admin/devise/sessions#verify_otp', as: :verify_otp
  get '/admin/enter_otp', to: 'active_admin/devise/sessions#enter_otp', as: :enter_otp
  post '/admin/validate_otp', to: 'active_admin/devise/sessions#validate_otp', as: :validate_otp
end

ActiveAdmin.routes(self)

2. 完善会话控制器的逻辑

你现在的create方法缺少对密码错误/用户不存在的处理,会导致异常跳转,建议补充完善;同时添加后续OTP相关的控制器方法:

# config/initializers/active_admin_devise_sessions_controller.rb
class ActiveAdmin::Devise::SessionsController
 include ::ActiveAdmin::Devise::Controller

 def create
  if params[:admin_user].present?
    email = params[:admin_user][:email]
    password = params[:admin_user][:password]
    admin_user = AdminUser.find_by_email(email)

    if admin_user.present? && admin_user.valid_password?(password)
      @admin_user = admin_user
      redirect_to verification_method_path
    else
      # 处理登录失败的情况
      flash[:error] = '邮箱或密码错误,请重试'
      redirect_to new_admin_user_session_path
    end
  else
    flash[:error] = '请填写登录信息'
    redirect_to new_admin_user_session_path
  end
 end

 def verification_method
  # 渲染验证方式选择视图,无需额外逻辑
 end

 def verify_otp
  @admin_user = AdminUser.find(params[:admin_user_id])
  verification_method = params[:verification_method]
  
  # 生成6位OTP并设置过期时间
  otp = rand(100000..999999)
  @admin_user.update!(otp: otp, otp_expires_at: 10.minutes.from_now)

  # 根据选择的方式发送OTP(这里示例用邮件,短信逻辑自行替换)
  if verification_method == 'email'
    AdminMailer.send_otp(@admin_user, otp).deliver_now
  else
    # 调用短信服务发送OTP,比如:
    # SmsNotificationService.send_otp(@admin_user.phone, otp)
  end

  redirect_to enter_otp_path(admin_user_id: @admin_user.id), notice: '验证码已发送'
 end

 def enter_otp
  @admin_user = AdminUser.find(params[:admin_user_id])
 end

 def validate_otp
  @admin_user = AdminUser.find(params[:admin_user_id])
  submitted_otp = params[:otp].to_i

  if @admin_user.otp == submitted_otp && @admin_user.otp_expires_at > Time.current
    # OTP验证通过,完成登录
    sign_in(@admin_user, scope: :admin_user)
    redirect_to admin_root_path, notice: '登录成功'
  else
    flash[:error] = '验证码无效或已过期,请重试'
    redirect_to enter_otp_path(admin_user_id: @admin_user.id)
  end
 end
end

3. 创建对应的视图文件

在app/views/active_admin/devise/sessions目录下创建以下视图:

verification_method.html.erb(选择验证方式)

<div class="active_admin login">
  <h3>请选择验证方式</h3>
  <%= form_tag verify_otp_path, method: :post do %>
    <%= hidden_field_tag :admin_user_id, @admin_user.id %>
    
    <div class="field">
      <%= radio_button_tag :verification_method, 'email', checked: true %>
      <%= label_tag :verification_method_email, '邮箱验证' %>
    </div>
    
    <div class="field">
      <%= radio_button_tag :verification_method, 'phone' %>
      <%= label_tag :verification_method_phone, '手机验证' %>
    </div>
    
    <div class="actions">
      <%= submit_tag '发送验证码', class: 'button' %>
    </div>
  <% end %>
</div>

enter_otp.html.erb(输入OTP)

<div class="active_admin login">
  <h3>请输入验证码</h3>
  <%= form_tag validate_otp_path, method: :post do %>
    <%= hidden_field_tag :admin_user_id, @admin_user.id %>
    
    <div class="field">
      <%= label_tag :otp, '6位验证码' %>
      <%= text_field_tag :otp, nil, placeholder: '请输入验证码', required: true %>
    </div>
    
    <div class="actions">
      <%= submit_tag '验证登录', class: 'button' %>
    </div>
  <% end %>
</div>

4. 添加AdminUser模型的OTP字段

生成迁移文件添加OTP相关字段:

rails generate migration AddOtpFieldsToAdminUsers otp:integer otp_expires_at:datetime
rails db:migrate

额外注意事项

  • 确保你的邮件/短信服务配置正确,AdminMailer需要自行创建(比如生成一个发送OTP的邮件模板)。
  • 可以给OTP字段添加索引,或者在验证时添加更严格的逻辑(比如限制发送次数)。
  • 测试时记得清除浏览器缓存,避免路由缓存导致的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:57:04