做网站开发时,总会遇到这样的场景:普通ref="/tag/127/" style="color:#643D3D;font-weight:bold;">用户只能看自己的订单,管理员却要能管理所有数据。这时候,权限控制就成了绕不开的一环。在 Ruby on Rails 项目里,怎么干净利落地实现这套机制?其实没那么复杂。
从最简单的角色判断开始
很多小项目一开始用户角色不多,比如就“普通用户”和“管理员”两种。这时候完全可以用一个 role 字段搞定。给 User 模型加个字段:
add_column :users, :role, :string, default: 'user'
然后在控制器里判断:
def edit_order
@order = Order.find(params[:id])
unless current_user.role == 'admin' || @order.user == current_user
redirect_to root_path, alert: '你没有权限操作此订单'
return
end
end
这种写法简单直接,适合功能不复杂的站点,改起来也快。
用 Pundit 让权限逻辑更清晰
随着功能变多,控制器里塞一堆 if 判断会越来越乱。这时候可以引入 Pundit。它把权限判断抽成独立的 Policy 类,结构更清晰。
先在 Gemfile 加上:
gem 'pundit'
安装后生成一个订单相关的策略文件:
rails generate pundit:policy order
编辑生成的 policy 文件:
class OrderPolicy < ApplicationPolicy
def edit?
user.admin? || record.user == user
end
def update?
edit?
end
end
控制器里就能变得清爽:
class OrdersController < ApplicationController
include Pundit::Authorization
def edit
@order = Order.find(params[:id])
authorize @order
rescue Pundit::NotAuthorizedError
redirect_to root_path, alert: '权限不足'
end
end
Pundit 的好处是,所有“谁能做什么”的逻辑都集中在 policy 文件里,以后查问题或者加新角色,一眼就能找到地方改。
处理菜单和页面元素的显示
权限不只是拦请求,前端也得配合。比如管理员才看得见的“后台入口”,普通用户根本不该看到。
在视图里可以直接用 Pundit 提供的 helper:
<% if policy(Order.new).create? %>
<%= link_to '新建订单', new_order_path %>
<% end %>
这样链接的显示与否由权限策略决定,前后端规则保持一致,不容易出错。
别忘了测试你的权限逻辑
权限一旦出漏洞,轻则数据错乱,重则信息泄露。写点测试很必要。
比如测一下普通用户不能编辑别人订单:
test 'normal user cannot edit other order' do
user = users(:normal)
other_order = orders(:from_other_user)
policy = OrderPolicy.new(user, other_order)
assert_not policy.edit?
end
几行测试能避免上线后半夜被报警电话叫醒。
根据业务灵活选择方案
不是每个项目都需要上 Pundit。如果只是几个简单的页面跳转控制,用 before_action 加个方法就够了。但如果你的后台系统越来越复杂,角色越来越多,早点把权限拆出去,后期维护会轻松不少。
实际开发中,见过太多人把权限判断散落在七八个控制器里,最后谁都不敢动。提前规划好,哪怕只是约定好判断逻辑统一放在某个 service 类里,都会让团队协作顺畅很多。