基于Next-Auth实现会话管理系统 支持修改密码时登出所有其他会话
Next-Auth多会话管理实现方案
1. 新增会话存储模型
首先创建会话数据表,用于存储所有用户的有效会话标识,新建models/Session.js:
import mongoose from "mongoose"; const SessionSchema = new mongoose.Schema({ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true, }, jti: { type: String, required: true, unique: true, }, expires: { type: Date, required: true, } }, { timestamps: true }); // 自动清理过期会话的索引 SessionSchema.index({ expires: 1 }, { expireAfterSeconds: 0 }); export const Session = mongoose.models.Session || mongoose.model("Session", SessionSchema);
2. 修改NextAuth配置
调整你的[...nextauth].js代码,新增会话存储、校验逻辑:
import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import mongoose from "mongoose"; import { compare } from "bcrypt"; import { User } from "../auth/signup"; // 引入刚才创建的会话模型,路径根据你的项目结构调整 import { Session } from "../../../models/Session"; export default NextAuth({ session: { strategy: "jwt", maxAge: 30 * 24 * 60 * 60, updateAge: 24 * 60 * 60, }, providers: [ CredentialsProvider({ async authorize(credentials){ await mongoose.connect(process.env.DB_URI); const result = await User.findOne({$or: [{email: credentials.username}, {username: credentials.username}]}); if(!result){ mongoose.connection.close(); throw new Error("Incorrect username or password."); } const checkPassword = await compare(credentials.password, result.password); if(!checkPassword){ mongoose.connection.close(); throw new Error("Incorrect username or password.") } if(!result.emailVerified.verified){ mongoose.connection.close(); throw new Error("Please verify your email adress.") } mongoose.connection.close(); return { uid: result._id }; } }), ], callbacks: { async jwt({ token, user }){ // 刚登录时,将当前会话存入数据库 if (user) { await mongoose.connect(process.env.DB_URI); await Session.create({ userId: user.uid, jti: token.jti, expires: new Date(token.exp * 1000) }); mongoose.connection.close(); token.user = user; return token; } // 每次校验token时,检查当前会话是否还存在于数据库 await mongoose.connect(process.env.DB_URI); const exists = await Session.findOne({ userId: token.user.uid, jti: token.jti }); mongoose.connection.close(); // 会话不存在则返回空,触发自动登出 if (!exists) return null; return token; }, async session({ session, token }){ session.user = token.user; // 把jti传到前端,后续登出其他会话需要用到 session.jti = token.jti; return session; } } });
3. 实现「登出所有其他会话」接口
新建pages/api/auth/logout-others.js:
import { getSession } from "next-auth/react"; import mongoose from "mongoose"; import { Session } from "../../../models/Session"; // 路径调整为你实际的模型路径 export default async function handler(req, res) { if (req.method !== "POST") { return res.status(405).json({ message: "方法不允许" }); } // 获取当前登录用户会话 const session = await getSession({ req }); if (!session) { return res.status(401).json({ message: "未登录" }); } await mongoose.connect(process.env.DB_URI); // 删除当前用户除了当前会话之外的所有有效会话 await Session.deleteMany({ userId: session.user.uid, jti: { $ne: session.jti } }); mongoose.connection.close(); return res.status(200).json({ message: "已登出所有其他设备" }); }
4. 修改密码时登出其他会话的实现
在你的修改密码接口中,验证旧密码通过、更新新密码之后,调用删除逻辑即可:
// ... 省略修改密码的原有逻辑,验证旧密码通过、更新密码后 await Session.deleteMany({ userId: 当前登录用户的uid, jti: { $ne: 当前会话的jti } // 如果要连当前会话也一起登出,直接删掉jti的判断条件即可 });
5. 前端按钮实现
在需要的位置添加触发按钮:
const logoutOthers = async () => { await fetch("/api/auth/logout-others", { method: "POST" }); alert("已登出所有其他设备"); } // 页面组件中添加 <button onClick={logoutOthers}>登出所有其他会话</button>
内容的提问来源于stack exchange,提问作者Soufiane
相关产品推荐
相关产品推荐

