认证
理解认证对于保护应用数据至关重要。本页将指导您使用React和Next.js的功能来实现认证。
开始之前,有助于将过程分解为三个概念:
下图显示了使用React和Next.js功能进行认证流程的图示:
本页的示例将介绍基本的用户名和密码认证,以教育为目的。虽然您可以实现自定义认证解决方案,但为了提高安全性和简化操作,我们建议使用认证库。这些库提供了内置的认证、会话管理和授权解决方案,以及社交登录、多因素认证和基于角色的访问控制等其他功能。您可以在认证库部分找到列表。
认证
## 会话管理会话管理确保用户的认证状态在请求之间得以保持。它涉及创建、存储、刷新和删除会话或令牌。
有两种类型的会话:
- 无状态:会话数据(或令牌)存储在浏览器的cookie中。cookie随每个请求一起发送,允许在服务器上验证会话。这种方法更简单,但如果不正确实现,可能不太安全。
- 数据库:会话数据存储在数据库中,用户的浏览器只接收加密的会话ID。这种方法更安全,但可能更复杂,并且使用更多的服务器资源。
须知: 尽管您可以使用任一方法,或两种都用,我们建议使用会话管理库,如iron-session或Jose。
无状态会话
须知:在处理会话和cookie时,请确保您的应用程序遵循最佳安全实践,例如使用HTTPS和设置适当的cookie属性。
数据库会话
要创建和管理数据库会话,您需要按照以下步骤操作:
- 在数据库中创建一个表来存储会话和数据(或者检查您的认证库是否处理了这一点)。
- 实现插入、更新和删除会话的功能。
- 在将会话ID存储到用户浏览器之前进行加密,并确保数据库和cookie保持同步(这是可选的,但建议用于中间件中的乐观认证检查)。
一旦用户通过身份验证并创建会话,您可以实施授权来控制用户在您的应用程序中可以访问和执行的操作。
主要有两种类型的授权检查:
- 乐观型:使用存储在cookie中的会话数据检查用户是否有权访问路由或执行操作。这些检查适用于快速操作,例如显示/隐藏UI元素或根据权限或角色重定向用户。
- 安全型:使用存储在数据库中的会话数据检查用户是否有权访问路由或执行操作。这些检查更安全,用于需要访问敏感数据或操作。
对于这两种情况,我们建议:
- 创建一个数据访问层来集中您的授权逻辑
- 使用数据传输对象(DTO)仅返回必要的数据
- 可选地使用中间件执行乐观型检查。
乐观型检查与中间件(可选)
在某些情况下,您可能希望使用中间件并根据权限重定向用户:
- 执行乐观型检查。由于中间件在每个路由上运行,它是集中重定向逻辑和预先过滤未授权用户的好方法。
- 保护在用户之间共享数据的静态路由(例如付费墙后的内容)。
然而,由于中间件在每个路由上运行,包括预取路由,重要的是只从cookie中读取会话(乐观型检查),并避免数据库检查以防止性能问题。
例如:
import { NextRequest, NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'
// 1. 指定受保护和公共路由
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
export default async function middleware(req: NextRequest) {
// 2. 检查当前路由是受保护还是公共
const path = req.nextUrl.pathname
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)
// 3. 从cookie解密会话
const cookie = cookies().get('session')?.value
const session = await decrypt(cookie)
// 5. 如果用户未通过身份验证,则重定向到 /login
if (isProtectedRoute && !session?.userId) {
return NextResponse.redirect(new URL('/login', req.nextUrl))
}
// 6. 如果用户已通过身份验证,则重定向到 /dashboard
if (
isPublicRoute &&
session?.userId &&
!req.nextUrl.pathname.startsWith('/dashboard')
) {
return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
}
return NextResponse.next()
}
// Routes Middleware 不应运行的路由
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
# middleware.js
```js
import { NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'
// 1. 指定受保护和公开的路由
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
export default async function middleware(req) {
// 2. 检查当前路由是受保护的还是公开的
const path = req.nextUrl.pathname
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)
// 3. 从cookie解密会话
const cookie = cookies().get('session')?.value
const session = await decrypt(cookie)
// 5. 如果用户未认证,则重定向到 /login
if (isProtectedRoute && !session?.userId) {
return NextResponse.redirect(new URL('/login', req.nextUrl))
}
// 6. 如果用户已认证,则重定向到 /dashboard
if (
isPublicRoute &&
session?.userId &&
!req.nextUrl.pathname.startsWith('/dashboard')
) {
return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
}
return NextResponse.next()
}
// Routes Middleware 应该不运行的路由
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
须知:虽然 Middleware 对于初始检查很有用,但它不应该是保护数据的唯一防线。大多数安全检查应该尽可能地靠近数据源执行,有关更多信息,请参见数据访问层。
技巧:
- 在 Middleware 中,您也可以使用
req.cookies.get('session).value
读取 cookie。- Middleware 使用 Edge Runtime,请检查您的 Auth 库和会话管理库是否兼容。
- 您可以在 Middleware 中使用
matcher
属性来指定 Middleware 应该运行在哪些路由上。尽管对于认证,建议 Middleware 在所有路由上运行。
须知:以上内容为示例,实际使用时应根据具体需求进行调整。
资源
既然您已经了解了 Next.js 中的认证,以下是与 Next.js 兼容的库和资源,可帮助您实现安全的认证和会话管理:
认证库
会话管理库
进一步阅读
要继续了解认证和安全性,请查看以下资源: