前言
在使用 Next.js14 开发登录登出功能时遇到一个问题:可以设置 cookie,但无法清除 cookie。
在开发这个功能时,借助了 Route Handler 的特性,关于 cookie 的处理,官网提到了 next/headers 的 cookies API;同样可以通过 Request、Response 和 NextRequest、NextResponse 提供的相关方法处理。NextRequest、NextResponse 来自 next/server,它们分别拓展了 Web Request API 和 Web Response API 的功能。
登录功能
我的 JWT 来自 API 接口,因此做了一些 BFF 的工作,下面是登录功能的实现:
ts
// app/api/login/route.ts
import { api } from '@/config/api';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.json();
const { email, password } = body;
const response = await fetch(`${api}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
password,
}),
});
if (response.status === 404) {
return NextResponse.json({
success: false,
msg: '请求地址不存在',
});
}
const json = await response.json(); // 解析出失败的 json 数据
if (!response.ok) {
return NextResponse.json(
{
success: false,
msg: json.message,
},
{ status: response.status || 500 }
);
}
const token = json.data.token;
return NextResponse.json(
// 返回body内容
{
success: true,
msg: json.message,
},
// 设置其他
{
headers: {
'Set-Cookie': `token=${token};path=/;max-age=86400;HttpOnly`,
},
}
);
}
调用 NextResponse.json 方法,第一个参数为返回给前端 json 数据,第二个参数用来配置请求头、状态码。在服务端设置响应头 Set-Cookie 后,发送给客户端,以后客户端就会自动带上请求头 Cookie。在上面代码中,Set-Cookie 中最重要的参数就是 token,它的值来自 API 中返回的 jwt 字符串。
- 登录时,响应头中加入了 Set-Cookie:
- Cookie 中存入了 token:
- 发请求时,请求头中存在 Cookie:
登出功能
登出时,需要把 cookie 删除,于是我使用了 NextRequest 和 NextResponse 中提供的 request.cookie.delete方法和 response.cookie.delete 方法:
ts
request.cookies.delete('token');
let response = NextResponse.next();
response.cookies.delete('token');
但这并不起作用。
下面是有效的代码:
ts
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
export async function DELETE() {
// 设置过期时间为0来删除cookie
cookies().set('token', '', { maxAge: 0 });
return NextResponse.json({
success: true,
msg: '登出成功',
});
}
调用 next/headers 中的 cookies 成功地删除了 cookies,实现了登出功能。
路由中间件
在 Next.js 中的中间件是介于服务器和应用之间的一层介质。当一个请求到达应用时,它会首先通过中间件。然后中间件可以根据这个请求的信息(比如URL、headers、cookies等)执行一些动作,然后有选择的转发这个请求到你的页面或者api路由,或者直接返回一个响应。
有了中间件,你就可以在一个集中的地方处理跨越多个页面或API路由的逻辑。比如:
- 认证和授权:检查请求的 cookie 或 header,来确认用户的身份,然后决定他是否有权限访问请求的资源。
- 重定向和路由改写
- 自定义的缓存规则
下面是检查认证和重定向的例子:
ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
// 检查 token(从cookie中获取token)
const token = request.cookies.get('token')?.value;
// 没有 token 时,跳回登录页
if (!token) {
const url = request.nextUrl.clone();
url.pathname = '/login';
return NextResponse.redirect(url);
}
}
如果未登录,则重定向到登录页。
为什么使用 cookie?
要清楚 Route Handler 属于服务器层面,这里无法使用浏览器提供的window对象,也就无法使用HTML5的 localStorage 和 sessionStorage 作为 token 的存储方案。路由中间件 middleware.ts 也在服务器中,同样无法调用浏览器中的 localStorage 等 API。
综上,在客户端和服务端都能够使用的存储方案只有 cookie。
技术交流:
公众号:见嘉 Being Dev
v:with_his_x