You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 14中如何将中间件获取的用户权限与menu.tsx菜单项集成?

在Next.js 14中实现权限驱动的动态菜单

核心思路

因为权限存储在HTTP-only Cookie的accessToken中,只能在服务器端读取,所以要通过服务器组件或中间件传递权限到服务器端,再基于权限过滤静态菜单数组,实现动态显示/隐藏。


方案一:服务器组件直接读取Cookie解析权限(推荐)

利用Next.js 14 App Router的服务器组件特性,直接读取Cookie解析权限,过滤菜单后渲染。

步骤1:扩展菜单类型,添加权限字段

修改menu.tsx,给需要权限控制的菜单项添加requiredPermission字段:

// menu.tsx
import { Gauge, Users } from 'lucide-react';

export interface MenuProps {
  href: string;
  icon: React.ReactNode;
  title: string;
  requiredPermission?: string; // 新增:该菜单项所需权限
}

export const Menu: MenuProps[] = [
  {
    href: '/dashboard',
    icon: <Gauge size={20} />,
    title: 'Dashboard',
    requiredPermission: 'Dashboard.Read'
  },
  {
    href: '/users',
    icon: <Users size={20} />,
    title: '用户管理',
    requiredPermission: 'User.Read'
  },
  // 其他无需权限的菜单项可以省略该字段
];

步骤2:创建服务器组件处理权限与菜单过滤

创建ServerSideMenu.tsx服务器组件,读取Cookie解析权限并过滤菜单:

// app/components/ServerSideMenu.tsx
import { cookies } from 'next/headers';
import { Menu as RawMenu } from '../menu';

// 解析accessToken获取权限的工具函数
function parsePermissionsFromToken(token: string): string[] {
  try {
    // 示例:JWT解码(根据你的token格式调整)
    const payload = JSON.parse(atob(token.split('.')[1]));
    return payload.permissions || [];
  } catch (err) {
    console.error('解析权限失败:', err);
    return [];
  }
}

export default function ServerSideMenu() {
  // 从Cookie读取accessToken
  const accessToken = cookies().get('accessToken')?.value;
  const userPermissions = accessToken ? parsePermissionsFromToken(accessToken) : [];

  // 根据权限过滤菜单
  const filteredMenu = RawMenu.filter(item => {
    // 无权限要求的菜单项直接显示
    if (!item.requiredPermission) return true;
    // 检查用户是否拥有所需权限
    return userPermissions.includes(item.requiredPermission);
  });

  // 渲染菜单UI
  return (
    <nav className="sidebar">
      <ul>
        {filteredMenu.map(item => (
          <li key={item.href} className="menu-item">
            <a href={item.href}>
              {item.icon}
              <span>{item.title}</span>
            </a>
          </li>
        ))}
      </ul>
    </nav>
  );
}

步骤3:在布局/页面中使用服务器组件

在根布局或需要显示菜单的页面中引入该组件:

// app/layout.tsx
import ServerSideMenu from './components/ServerSideMenu';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh-CN">
      <body>
        <ServerSideMenu />
        <main className="content">{children}</main>
      </body>
    </html>
  );
}

方案二:中间件传递权限到服务器组件

如果已在中间件中解析权限,可以通过自定义响应头将权限传递给服务器组件,再过滤菜单。

步骤1:修改中间件添加权限头

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const accessToken = request.cookies.get('accessToken')?.value;
  let permissions: string[] = [];

  if (accessToken) {
    // 复用你的权限解析逻辑
    try {
      const payload = JSON.parse(atob(accessToken.split('.')[1]));
      permissions = payload.permissions || [];
    } catch (err) {}
  }

  // 将权限转为JSON字符串,通过自定义头传递
  const response = NextResponse.next();
  response.headers.set('x-user-permissions', JSON.stringify(permissions));
  return response;
}

export const config = {
  matcher: ['/', '/dashboard/:path*', '/users/:path*'], // 匹配需要权限校验的路径
};

步骤2:服务器组件读取权限头过滤菜单

// app/components/ServerSideMenu.tsx
import { headers } from 'next/headers';
import { Menu as RawMenu } from '../menu';

export default function ServerSideMenu() {
  // 读取中间件添加的自定义头
  const permissionsHeader = headers().get('x-user-permissions');
  const userPermissions = permissionsHeader ? JSON.parse(permissionsHeader) : [];

  // 过滤菜单逻辑同方案一
  const filteredMenu = RawMenu.filter(item => {
    if (!item.requiredPermission) return true;
    return userPermissions.includes(item.requiredPermission);
  });

  // 渲染菜单UI...
}

方案三:服务器组件传递权限给客户端组件

如果菜单需要客户端交互(比如折叠、高亮),可以在服务器组件中解析权限,再传递给客户端组件使用:

步骤1:创建客户端菜单组件

// app/components/ClientMenu.tsx
'use client';

import { MenuProps } from '../menu';

interface ClientMenuProps {
  permissions: string[];
  menuItems: MenuProps[];
}

export default function ClientMenu({ permissions, menuItems }: ClientMenuProps) {
  const filteredMenu = menuItems.filter(item => {
    if (!item.requiredPermission) return true;
    return permissions.includes(item.requiredPermission);
  });

  // 这里可以添加客户端交互逻辑,比如点击折叠等
  return (
    <nav className="sidebar">
      <ul>
        {filteredMenu.map(item => (
          <li key={item.href} className="menu-item">
            <a href={item.href}>
              {item.icon}
              <span>{item.title}</span>
            </a>
          </li>
        ))}
      </ul>
    </nav>
  );
}

步骤2:服务器组件封装权限传递

// app/components/MenuWrapper.tsx
import { cookies } from 'next/headers';
import { Menu as RawMenu } from '../menu';
import ClientMenu from './ClientMenu';
import { parsePermissionsFromToken } from './utils'; // 复用解析函数

export default function MenuWrapper() {
  const accessToken = cookies().get('accessToken')?.value;
  const permissions = accessToken ? parsePermissionsFromToken(accessToken) : [];

  return <ClientMenu permissions={permissions} menuItems={RawMenu} />;
}

注意事项

  1. HTTP-only Cookie安全:所有权限解析逻辑都在服务器端完成,避免客户端接触accessToken,符合安全要求。
  2. 权限解析容错:添加异常捕获,防止token无效导致菜单加载失败。
  3. 菜单项默认显示:未添加requiredPermission的菜单项默认显示,无需额外配置。

内容的提问来源于stack exchange,提问作者hantoren

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 04:35:55