Next.js应用中的授权机制

January 1, 2025 (3mo ago)

Next.js应用中的授权机制

在Next.js应用的开发进程中,搭建授权机制是筑牢应用安全防线,保障用户数据安全,确保用户对功能访问合规的核心任务。此前,Vercel CEO与Cloudflare CEO曾就一起安全事件公开互怼,这一事件也促使相关方反思和改进安全策略。本文不仅围绕此次事件展开探讨,同时也参考了Next.js开发团队所推荐的安全实现方案 ,旨在为开发者提供多维度的安全指引,助力打造更安全、可靠的Next.js应用。

一、数据访问授权

在全栈应用开发中,数据访问授权用以控制对数据的读取和写入操作,这一过程通常在API层完成。

1. 小型应用的实现

对于规模较小的应用,可在数据获取函数或服务器操作内进行授权检查。以获取文章列表getPosts和创建文章createPost为例:

import { getAuth } from './auth';
 
async function getPosts() {
    const session = await getAuth();
    if (!session) {
        throw new Error('未授权');
    }
    // 获取文章数据的逻辑
}
 
async function createPost(post) {
    const session = await getAuth();
    if (!session) {
        throw new Error('未授权');
    }
    // 创建文章的逻辑
}

getAuth函数针对数据库执行会话 Cookie 的验证。值得注意的是,我们在这里使用React 的cache API,以便在一个渲染周期内记忆化getAuth函数的结果:

export const getAuth = cache(async () => {
  const sessionToken =
    (await cookies()).get("session")?.value ?? null;
 
  if (!sessionToken) {
    return {
      user: null,
      session: null,
    };
  }
 
  // "expensive" database call
  return await validateSession(sessionToken);
});

2. 大型应用的分层授权

当应用规模增大,引入服务层和数据访问层后,授权过程可分层进行。API层验证用户身份,服务层执行核心授权逻辑,数据访问层作为最终的数据保护屏障。例如,在一个包含多层架构的博客应用中:

// API层示例
import { getAuth } from './auth';
import { getPostsService } from './services/postService';
 
async function apiGetPosts(req, res) {
    const session = await getAuth();
    if (!session) {
        return res.status(401).json({ error: '未授权' });
    }
    try {
        const posts = await getPostsService();
        return res.status(200).json(posts);
    } catch (error) {
        return res.status(500).json({ error: '服务器错误' });
    }
}
 
// 服务层示例
async function getPostsService() {
    // 在此添加业务规则和权限检查
    return await getPostsData();
}
 
// 数据访问层示例
async function getPostsData() {
    // 实际查询数据库获取文章数据的逻辑
}

二、路由授权

1. 页面组件内授权

在Next.js应用中,可在页面组件内进行授权检查,阻止未授权用户访问特定路由。以PostsPage组件为例:

import { getAuth } from './auth';
 
async function PostsPage() {
    const session = await getAuth();
    if (!session) {
        return <Redirect to="/login" />;
    }
    // 页面内容
}

2. 布局组件授权

为了简化授权逻辑,可将授权检查移至布局组件,如AuthLayout

import { getAuth } from './auth';
 
async function AuthLayout({ children }) {
    const session = await getAuth();
    if (!session) {
        return <Redirect to="/login" />;
    }
    return <>{children}</>;
}

然而,在布局组件中进行授权存在安全风险,因为页面组件可能会独立被获取。为应对这一问题,可采用以下策略:

3. 角色和权限管理

当应用涉及不同角色和权限时,可设置多个布局组件,如AdminLayout

import { getAuth } from './auth';
 
async function AdminLayout({ children }) {
    const session = await getAuth();
    if (!session ||!session.user.isAdmin) {
        return <Redirect to="/" />;
    }
    return <>{children}</>;
}

三、用户界面授权

UI授权用于提升用户体验,根据用户授权状态隐藏或禁用特定UI元素。以PostMoreMenu组件为例:

import { getAuth } from './auth';
 
function PostMoreMenu() {
    const { user } = getAuth();
    return (
        <div>
            <button>分享</button>
            {user && <button>删除</button>}
        </div>
    );
}

需注意,UI授权并非安全机制,恶意用户可操纵客户端代码绕过限制,核心授权检查仍需在API、服务和数据访问层进行。

四、中间件授权

在Next.js中,可使用中间件保护页面和路由免受未授权访问:

import { NextResponse } from 'next/server';
import { getAuth } from './auth';
 
export async function middleware(req) {
    const session = await getAuth();
    if (!session) {
        return NextResponse.redirect(new URL('/login', req.url));
    }
    return NextResponse.next();
}

但中间件在每个请求时都会执行,若进行数据库查询会影响性能。因此,建议进行预检检查,如检查会话cookie是否存在。

你也可以在中间件中刷新 Cookie 的过期时间,这是一种常见的做法,可以让用户在更长时间内保持登录状态。

此外,中间件运行在边缘运行时环境,与Node.js环境不同,存在一定限制。不过Next团队也在致力于切换为在 Node.js 中运行中间件

而且Next.js应用仅支持一个中间件文件,这对复杂应用的细粒度控制提出了挑战。不过Next团队正在致力于一个名为Interceptors的解决方案。