如何在Laravel中基于用户权限过滤注入InertiaJS的Ziggy路由?
问题描述
我正在开发一个基于Laravel、InertiaJS和ReactJS的项目,通过Ziggy Routes向前端注入路由,同时使用了Spatie/Laravel-Permissions权限包。目前的问题是Ziggy会把所有路由都注入到前端,存在潜在安全风险。我需要过滤注入的路由,只保留用户有权访问的部分:
- 所有
client.*前缀的路由都允许访问 admin.*前缀的路由需要根据用户权限筛选- 同时项目中还有fortify、horizon及debugbar相关路由需要处理
当前HandleInertiaRequests.php中间件的代码如下:
public function share(Request $request): array { return [ ...parent::share($request), 'auth' => [ 'user' => $request->user(), 'roles' => Auth::check() ? Auth::user()->getRoleNames() : [], 'permissions' => Auth::check() ? Auth::user()->getAllPermissions() : [], ], 'flash' => function () use ($request) { return [ 'success' => $request->session()->get('success'), 'error' => $request->session()->get('error'), ]; }, 'ziggy' => fn () => [ ...(new Ziggy)->toArray(), 'location' => $request->url(), ], 'csrf_token' => csrf_token(), ]; }
最佳实现方案
核心思路是在Ziggy生成路由数组后,根据用户权限和路由命名规则对路由进行过滤,只保留符合条件的路由注入到前端。
步骤1:编写路由过滤逻辑
在HandleInertiaRequests类中新增路由过滤方法,针对不同前缀的路由设置差异化规则:
client.*:直接保留所有该前缀路由admin.*:匹配用户权限(可根据自身权限命名规则调整匹配逻辑)- fortify路由:保留认证相关的公开路由,登录用户可访问全部fortify路由
- horizon/debugbar路由:仅在本地开发环境保留,生产环境直接过滤
- 未登录用户:仅保留公开路由(如fortify登录、注册)和
client.*中的公开路由(按需调整)
步骤2:修改HandleInertiaRequests代码
修改后的完整代码示例:
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; use Ziggy\Ziggy; public function share(Request $request): array { return [ ...parent::share($request), 'auth' => [ 'user' => $request->user(), 'roles' => Auth::check() ? Auth::user()->getRoleNames() : [], 'permissions' => Auth::check() ? Auth::user()->getAllPermissions()->pluck('name')->toArray() : [], ], 'flash' => function () use ($request) { return [ 'success' => $request->session()->get('success'), 'error' => $request->session()->get('error'), ]; }, 'ziggy' => fn () => [ ...$this->filterZiggyRoutes(), 'location' => $request->url(), ], 'csrf_token' => csrf_token(), ]; } /** * 过滤Ziggy路由,仅保留用户有权访问的路由 */ private function filterZiggyRoutes(): array { $ziggy = new Ziggy; $allRoutes = $ziggy->toArray()['routes']; $filteredRoutes = []; $isLoggedIn = Auth::check(); $permissions = $isLoggedIn ? Auth::user()->getAllPermissions()->pluck('name')->toArray() : []; foreach ($allRoutes as $name => $route) { // 保留所有client.*前缀路由 if (Str::startsWith($name, 'client.')) { $filteredRoutes[$name] = $route; continue; } // 过滤admin.*路由:检查用户是否拥有对应权限 if (Str::startsWith($name, 'admin.')) { // 示例:权限命名规则为"access_"+路由名称(替换点为下划线),可按需调整 $requiredPermission = 'access_' . str_replace('.', '_', $name); if ($isLoggedIn && in_array($requiredPermission, $permissions)) { $filteredRoutes[$name] = $route; } continue; } // 处理fortify路由:保留公开认证路由,登录用户可访问全部 if (Str::startsWith($name, 'fortify.')) { $publicFortifyRoutes = ['fortify.login', 'fortify.register', 'fortify.password.request', 'fortify.password.email']; if (in_array($name, $publicFortifyRoutes) || $isLoggedIn) { $filteredRoutes[$name] = $route; } continue; } // 仅本地环境保留horizon和debugbar路由 if (Str::startsWith($name, 'horizon.') || Str::startsWith($name, 'debugbar.')) { if (app()->environment('local')) { $filteredRoutes[$name] = $route; } continue; } // 其他路由:可添加自定义规则,比如保留所有GET方法的公开路由 // if (in_array('GET', $route['methods']) && !Str::startsWith($name, 'admin.')) { // $filteredRoutes[$name] = $route; // } } return [ 'routes' => $filteredRoutes, 'url' => $ziggy->url(), 'port' => $ziggy->port(), 'domain' => $ziggy->domain(), 'prefix' => $ziggy->prefix(), 'defaults' => $ziggy->defaults(), ]; }
关键说明
- 权限匹配逻辑:示例中假设权限命名为
access_admin_dashboard对应路由admin.dashboard,你可以根据项目实际权限规则调整$requiredPermission的生成方式,比如直接用路由名称作为权限名,或者维护路由-权限映射表。 - 环境控制:horizon和debugbar路由仅在本地环境暴露,避免生产环境泄露敏感运维路由。
- 性能优化:如果项目路由数量庞大,可以考虑缓存用户有权访问的路由列表,减少每次请求的过滤计算开销。
内容的提问来源于stack exchange,提问作者Emre Can Çakıroğlu
相关产品推荐
相关产品推荐

