使用Pundit和Devise时,如何修复未登录用户访问index动作的授权问题?
这个问题我之前也碰到过,核心问题是未登录时current_user为nil,导致你可能没给@wikis赋值就调用了authorize,或者@wikis直接变成nil,而Pundit需要一个对象(或集合)来匹配对应的Policy。给你几个解决方案,你可以根据自己的需求选择:
方案1:先强制用户登录(最简单直接)
如果你的需求就是未登录用户完全不能访问Wiki列表页面,那直接用Devise的authenticate_user!过滤器就行,把它放在wikis_controller顶部:
class WikisController < ApplicationController before_action :authenticate_user!, only: [:index] # 只在index动作生效 def index @wikis = current_user.wikis # 此时current_user肯定存在,不会有nil问题 authorize @wikis end end
这样未登录用户访问/wikis时,会直接被Devise重定向到登录页面,根本不会进入index动作的逻辑,自然就不会出现@wikis未定义的问题。
方案2:用Pundit的Policy Scope控制可见范围(更灵活)
如果以后可能需要给未登录用户开放部分权限(比如查看公开Wiki),或者想通过Pundit统一管理权限,那推荐用policy_scope来处理@wikis的赋值,同时在Policy里定义index?权限:
第一步:修改WikisController的index动作
def index @wikis = policy_scope(Wiki) # 交给Pundit的Scope来决定用户能看到哪些Wiki authorize @wikis # 这里@wikis永远是一个集合(哪怕是空的),不会是nil end
第二步:完善WikiPolicy
class WikiPolicy < ApplicationPolicy # 控制是否允许访问index页面 def index? user.present? # 只允许登录用户访问,未登录用户直接返回false end # 控制用户能看到的Wiki集合 class Scope < Scope def resolve if user.present? # 登录用户可以查看自己的Wiki,或者公开的Wiki(根据你的业务调整) scope.where(user: user).or(scope.where(public: true)) else # 未登录用户返回空集合(因为index?已经拦截了,这里其实不会被执行到) scope.none end end end end
第三步:全局处理未授权错误
在application_controller.rb里添加Pundit的错误捕获,给用户友好提示:
class ApplicationController < ActionController::Base include Pundit::Authorization # 捕获Pundit的未授权错误 rescue_from Pundit::NotAuthorizedError, with: :handle_not_authorized private def handle_not_authorized flash[:alert] = "你需要先登录才能访问Wiki列表哦!" redirect_to new_user_session_path # 跳转到登录页面 end end
这样未登录用户访问时,authorize @wikis会触发NotAuthorizedError,然后被重定向到登录页,同时@wikis因为policy_scope的存在,永远是一个ActiveRecord集合,不会出现undefined的问题。
方案3:手动处理未登录场景(适合简单需求)
如果不想用Policy Scope,也可以在controller里先判断用户登录状态,给@wikis一个兜底值:
def index @wikis = user_signed_in? ? current_user.wikis : Wiki.none authorize @wikis end
然后在WikiPolicy里还是保留index?方法:
def index? user.present? end
这个方法比较直接,适合逻辑简单的场景,但扩展性不如方案2。
最后提醒一句:不管用哪个方案,一定要确保authorize调用之前@wikis已经被赋值,哪怕是一个空集合,不能让它是nil,否则Pundit无法找到对应的Policy方法,就会报错。
内容的提问来源于stack exchange,提问作者kolbykskk

