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

Rails强制用户改密码时Before action回调多次重定向如何解决

问题原因

  1. 密码修改状态判断逻辑缺陷
  • 你当前用created_at == password_changed_at判断用户是否未修改过密码,存在两个核心问题:
    • 若用户创建时password_changed_at未赋值为created_at(Devise默认创建用户时如果没有显式处理,password_changed_at可能为nil),该判断条件要么永远不成立无法触发跳转,要么修改密码后未更新该字段导致条件持续成立反复触发跳转。
    • 时间对比存在精度误差问题,数据库存储的时间和Ruby实例化的时间精度可能不一致,导致判定结果不符合预期。
  1. 未校验密码修改页的访问权限,可能触发权限拦截循环
  • 你在change_password方法里直接根据params[:id]查询用户,没有校验params[:id]是否等于当前登录的current_user.id,如果存在权限校验中间件(如Pundit/CanCanCan)禁止当前用户访问其他用户的密码修改页,会被拦截重定向到其他页面,其他页面再触发check_password跳转回密码修改页,形成重定向循环。
  1. 修改密码操作未更新password_changed_at字段
  • 你在update_password方法中更新密码时,只更新了password和password_confirmation,没有同步更新password_changed_at为当前时间,导致用户修改密码后判断条件依然成立,登录后反复被重定向到密码修改页。
  1. Devise会话控制器跳过校验范围不全
  • 目前只给destroy动作跳过了校验,登录相关的new、create动作也可能触发校验逻辑,导致登录流程异常。

修复方案

1. 修正密码未修改的判断逻辑,增加路径白名单双重校验

给check_password方法补充路径白名单判断,就算skip_before_action异常未生效也不会触发循环,同时优化判断条件:

class ApplicationController < ActionController::Base
  before_action :check_password, if: :current_user
  def check_password 
    # 跳过密码修改相关路径、Devise认证相关路径,避免循环
    return if [
      change_password_admin_user_path(current_user),
      update_password_admin_user_path(current_user),
      new_user_session_path,
      destroy_user_session_path
    ].include?(request.path)

    if current_user.present? && 
       (current_user.password_changed_at.nil? || current_user.password_changed_at <= current_user.created_at)
      redirect_to change_password_admin_user_path(current_user)
    end
  end
end

2. 补充密码修改操作的权限校验,同步更新密码修改时间

class Admin::UsersController < ApplicationController
  skip_before_action :check_password, only: [:change_password, :update_password]
     
  def change_password
    @user = User.find(params[:id])
    # 限制只能修改自己的密码,避免权限拦截导致循环
    unless @user == current_user
      flash[:alert] = "无权修改其他用户的密码"
      redirect_to root_path and return
    end
  end
    
  def update_password
    @user = User.find(params[:id])
    unless @user == current_user
      flash[:alert] = "无权修改其他用户的密码"
      redirect_to root_path and return
    end
    if @user.valid_password?(params[:old_password])
      # 更新密码时同步更新password_changed_at字段
      if @user.update_attributes(
           password: params[:new_password], 
           password_confirmation: params[:reenter_password],
           password_changed_at: Time.current
         )
        flash[:notice] = 'Password changed successfully, please login with new password'
        redirect_to new_user_session_path and return
      else
        flash[:alert] = @user.errors.full_messages.join(', ')
      end
    else
     flash[:alert] = "Old password you've entered is invalid"
    end
    redirect_to change_password_admin_user_path(@user.id)
  end
end

3. 补全Devise会话控制器的跳过校验范围

class Admin::SessionsController < Devise::SessionsController
  skip_before_action :check_password, only: [:new, :create, :destroy]
end

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.19 06:54:09