我正在构建一个 nextjs 13 管理仪表板,我只有一个问题,每当用户重新加载页面时,它就会重定向到登录页面,当检查开发人员模式时,cookie 仍然存在于 Cookie 部分,但会话存储和区域设置存储为空,并且每次登录令牌发生变化时 到目前为止我还没有实现中间件。
api/auth/[...nextauth]/route.js
import {authOptions} from '@/lib/auth'
import NextAuth from 'next-auth/next'
const handler = NextAuth(authOptions)
export {handler as GET, handler as POST}
/lib/auth.js
import { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { db } from "./db";
import { compare } from "bcryptjs";
export const authOptions = {
adapter: PrismaAdapter(db),
secret: process.env.NEXTAUTH_SECRET,
session: {
strategy: "jwt",
maxAge: 60 * 60 * 24 * 30,
},
pages: {
signIn: "/sign-in",
},
rememberMe: {
label: "Remember Me",
type: "checkbox",
},
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "[email protected]",
validation: {
required: true,
pattern:
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
},
},
password: { label: "Password", type: "password", placeholder: "****" },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
const existingUser = await db.user.findUnique({
where: { email: credentials?.email },
});
if (
(existingUser &&
existingUser.passwordExpirationDate &&
existingUser.passwordExpirationDate < new Date()) ||
(existingUser &&
existingUser.verificationCodeExpiresAt &&
existingUser.verificationCodeExpiresAt < new Date())
) {
return null; // Trigger password reset
}
if (!existingUser) {
return null;
}
const passwordMatch = await compare(
credentials.password,
existingUser.password
);
if (!passwordMatch) {
return null;
}
const passwordAgeInDays = existingUser.lastPasswordResetDate
? Math.floor(
(new Date().getTime() -
existingUser.lastPasswordResetDate.getTime()) /
(1000 * 60 * 60 * 24)
)
: 0;
if (passwordAgeInDays > 30) {
await db.user.update({
where: { id: existingUser.id },
data: { IsExpired: true },
});
return null;
}
const maxAge = credentials.rememberMe ? 60 * 60 * 24 * 30 : 60 * 60 * 24;
return {
id: `${existingUser.id}`,
name: existingUser.name,
username: existingUser.username,
email: existingUser.email,
role: existingUser.role,
exp: Math.floor(Date.now() / 1000) + maxAge,
};
},
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
return {
...token,
id: user.id,
username: user.username,
role: user.role,
};
}
return token;
},
async session({ session, token }) {
const maxAge = token.exp - Math.floor(Date.now() / 1000);
return {
...session,
user: {
...session.user,
id: token.id,
username: token.username,
role: token.role,
},
expires: Math.floor(Date.now() / 1000) + maxAge,
};
},
},
};
我想保持用户登录状态,并管理用户会话
理解问题:
页面重新加载后,尽管存在 cookie,用户仍会被重定向到登录页面。 重新加载后会话存储和本地存储为空。 每次登录时登录令牌都会发生变化(这对于持久会话来说是不可取的)。 根本原因(潜在):
Cookie 中安全标志缺失或不正确:使用 HTTPS 时,安全标志对于纯 HTTP Cookie 至关重要,以确保它们仅通过安全连接传输。这对于会话安全至关重要。 客户端存储滥用:虽然 cookie 旨在用于会话管理,但会话存储和本地存储不适合持久化用户会话,因为它们缺乏服务器端持久性并且可以由用户清除。 令牌刷新机制未实现:在令牌过期之前刷新令牌有助于维持持久会话,而无需持续进行身份验证。 解决方案:
设置 Cookie 的安全标志(需要 HTTPS):
在 NextAuth 配置 (/lib/auth.js) 中,确保 session.cookie 对象包含设置为 true 的 secure 属性:
JavaScript
authOptions = {
// ... other options
session: {
strategy: "jwt",
maxAge: 60 * 60 * 24 * 30,
cookie: {
secure: process.env.NODE_ENV === "production", // Set to true in production
},
},
// ...
};
说明:这可确保 cookie 仅通过 HTTPS 连接传输,从而增强安全性。
服务器端保留会话数据:
利用 NextAuth 的内置 JWT 功能进行会话管理。 NextAuth 将会话数据(用户信息)安全地存储在签名的 JWT 令牌(JSON Web 令牌)中。当用户发出后续请求时,包含令牌的 cookie 会一起发送,从而允许 NextAuth 验证用户的会话。
将 NextAuth 的 session.strategy 配置为“jwt”,就像您已经完成的那样。 在 API 路由中实现自定义逻辑,以在令牌过期之前更新令牌,从而确保持续的身份验证。 (有关更多详细信息,请参阅 NextAuth 文档:https://next-auth.js.org/configuration/nextjs) 地址更改登录令牌:
JWT 令牌有一个过期时间(在会话配置中使用 maxAge 设置)。 NextAuth 在身份验证成功后自动生成新令牌。考虑在客户端实现令牌刷新机制,以便在当前令牌过期之前获取新令牌:
使用 NextAuth 的内置 useSession 挂钩来访问组件中的会话数据。 监控令牌的过期时间。 当令牌接近到期时,向 NextAuth 的 /api/auth/refresh 端点(通常由 NextAuth 提供)发起刷新请求以获取新令牌。