如何为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
相关产品推荐
相关产品推荐

