ApplicationController未捕获Pundit Not Authorized Error问题排查
问题背景
你在使用Rails 5.2、Pundit 1.1和Rails Admin 1.2开发项目时,已经在application_controller.rb中配置了捕获Pundit::NotAuthorizedError并跳转首页提示,但非管理员用户访问Rails Admin仪表盘时,这个异常却没有被触发——预期的跳转和授权提示完全没出现。
你的相关代码如下:
application_controller.rb
class ApplicationController < ActionController::Base protect_from_forgery with: :exception include Pundit rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private def user_not_authorized(exception) flash[:alert] = "You are not authorized to perform this action." redirect_to root_path end end
application.policy.rb
class ApplicationPolicy ... def rails_admin?(action) case action when :dashboard, :index, :show, :new, :edit, :destroy, :export, :history, :show_in_app user.admin? else raise ::Pundit::NotDefinedError, "unable to find policy #{action} for #{record}." end end end
config/initializers/rails_admin.rb
RailsAdmin.config do |config| ### Popular gems integration ## == Devise == config.authenticate_with do warden.authenticate! scope: :user end config.current_user_method(&:current_user) ## == Pundit == config.authorize_with :pundit end module RailsAdmin module Extensions module Pundit class AuthorizationAdapter def authorize(action, abstract_model = nil, model_object = nil) record = model_object || abstract_model && abstract_model.model if action && !policy(record).send(*action_for_pundit(action)) raise ::Pundit::NotAuthorizedError.new("not allowed to #{action} this #{record}") end @controller.instance_variable_set(:@_pundit_policy_authorized, true) end def authorized?(action, abstract_model = nil, model_object = nil) record = model_object || abstract_model && abstract_model.model policy(record).send(*action_for_pundit(action)) if action end def action_for_pundit(action) [:rails_admin?, action] end end end end end
问题根源
这事儿的核心原因很直白:Rails Admin自带的控制器并没有继承你编写的ApplicationController,它默认继承的是RailsAdmin::ApplicationController。你在自己的控制器里设置的rescue_from是全局捕获,但只对继承它的业务控制器生效——而Rails Admin的控制器不在这个范围内,所以抛出的Pundit::NotAuthorizedError根本没被你的捕获逻辑接住。
两种修复方案
方案1:给Rails Admin控制器单独添加异常捕获
直接在config/initializers/rails_admin.rb中,给Rails Admin的控制器注入异常处理逻辑:
RailsAdmin.config do |config| # 保留你原有的配置 config.authenticate_with do warden.authenticate! scope: :user end config.current_user_method(&:current_user) config.authorize_with :pundit # 新增:给Rails Admin控制器添加异常捕获 config.controller do rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private def user_not_authorized(exception) flash[:alert] = "You are not authorized to perform this action." redirect_to root_path end end end
这种方式不需要改动现有控制器结构,针对性解决当前问题。
方案2:让Rails Admin控制器继承你的ApplicationController
如果你希望Rails Admin复用ApplicationController里的所有公共逻辑(比如通用权限校验、日志处理、其他异常捕获等),可以直接指定Rails Admin的父控制器:
RailsAdmin.config do |config| # 保留原有配置... config.parent_controller = 'ApplicationController' end
这样Rails Admin的所有控制器都会继承你的ApplicationController,自然就能用上里面的rescue_from了。不过要注意,这种方式需要确保你的ApplicationController里的逻辑不会和Rails Admin的内置功能冲突。
两种方案都能实现你想要的效果:非管理员用户访问Rails Admin时,会被跳转到首页并显示授权提示。
内容的提问来源于stack exchange,提问作者chell

