Rails强制用户改密码时Before action回调多次重定向如何解决
问题原因
- 密码修改状态判断逻辑缺陷
- 你当前用
created_at == password_changed_at判断用户是否未修改过密码,存在两个核心问题:- 若用户创建时
password_changed_at未赋值为created_at(Devise默认创建用户时如果没有显式处理,password_changed_at可能为nil),该判断条件要么永远不成立无法触发跳转,要么修改密码后未更新该字段导致条件持续成立反复触发跳转。 - 时间对比存在精度误差问题,数据库存储的时间和Ruby实例化的时间精度可能不一致,导致判定结果不符合预期。
- 若用户创建时
- 未校验密码修改页的访问权限,可能触发权限拦截循环
- 你在
change_password方法里直接根据params[:id]查询用户,没有校验params[:id]是否等于当前登录的current_user.id,如果存在权限校验中间件(如Pundit/CanCanCan)禁止当前用户访问其他用户的密码修改页,会被拦截重定向到其他页面,其他页面再触发check_password跳转回密码修改页,形成重定向循环。
- 修改密码操作未更新
password_changed_at字段
- 你在
update_password方法中更新密码时,只更新了password和password_confirmation,没有同步更新password_changed_at为当前时间,导致用户修改密码后判断条件依然成立,登录后反复被重定向到密码修改页。
- 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
相关产品推荐
相关产品推荐

