You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 02:06:07