如何正确配置Next-Auth与Keycloak的会话同步及过期策略
Next-Auth + Keycloak 实现会话同步与超时控制
完全可以实现你要的3小时无活动登出、1天强制登出需求,并且保持两端会话同步。核心是让Next-Auth的会话生命周期与Keycloak的会话配置对齐,同时通过钩子函数和主动检查同步两端状态,以下是具体方案:
一、先理清Keycloak配置和Next-Auth的关联
- Keycloak后台的会话闲置时长(路径:Sessions > Session Settings > Session Idle Timeout)对应3小时无活动登出,最大会话时长(Session Max Lifetime)对应1天强制登出。这两个设置直接决定Keycloak颁发的
access_token和refresh_token有效期,是同步两端会话的基础。 - Next-Auth默认只会利用Keycloak返回的令牌初始化自身会话,但不会主动监听Keycloak的会话过期事件,这就是之前两端会话不同步的原因。
二、核心配置步骤
1. 对齐Next-Auth会话与Keycloak令牌规则
修改[...nextauth].js配置,采用JWT策略管理会话,并实现令牌自动刷新逻辑:
import NextAuth from "next-auth"; import KeycloakProvider from "next-auth/providers/keycloak"; export default NextAuth({ providers: [ KeycloakProvider({ clientId: process.env.KEYCLOAK_CLIENT_ID, clientSecret: process.env.KEYCLOAK_CLIENT_SECRET, issuer: process.env.KEYCLOAK_ISSUER, // 必须请求offline_access scope,才能拿到可刷新的refresh_token authorization: { params: { scope: "openid profile email offline_access", }, }, }), ], session: { // 使用JWT策略,依赖Keycloak令牌而非Next-Auth自有cookie strategy: "jwt", // 会话最大时长设为1天(86400秒),和Keycloak的Session Max Lifetime保持一致 maxAge: 86400, }, callbacks: { async jwt({ token, user, account }) { // 首次登录时,保存Keycloak的令牌及过期时间 if (account && user) { return { ...token, accessToken: account.access_token, refreshToken: account.refresh_token, expiresAt: account.expires_at * 1000, // 转成毫秒格式 }; } // 令牌未过期(提前1分钟判断),直接返回 if (Date.now() < token.expiresAt - 60000) { return token; } // 令牌即将过期,调用Keycloak接口刷新 try { const response = await fetch(`${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_id: process.env.KEYCLOAK_CLIENT_ID, client_secret: process.env.KEYCLOAK_CLIENT_SECRET, grant_type: "refresh_token", refresh_token: token.refreshToken, }), }); const refreshedTokens = await response.json(); if (!response.ok) throw refreshedTokens; // 更新令牌信息 return { ...token, accessToken: refreshedTokens.access_token, refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, expiresAt: Date.now() + refreshedTokens.expires_in * 1000, }; } catch (error) { // 刷新失败,标记令牌错误,后续触发登出 return { ...token, error: "RefreshTokenError" }; } }, async session({ session, token }) { // 将令牌状态同步到session,供前端使用 session.user.accessToken = token.accessToken; session.error = token.error; // 如果令牌刷新失败,直接标记会话过期 if (token.error === "RefreshTokenError") { session.expires = new Date(0).toISOString(); } return session; }, async signOut({ token }) { // 登出时调用Keycloak接口,清除Keycloak端会话 await fetch(`${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/logout`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_id: process.env.KEYCLOAK_CLIENT_ID, client_secret: process.env.KEYCLOAK_CLIENT_SECRET, refresh_token: token.refreshToken, }), }); return true; }, }, });
2. 处理被动过期的会话同步
当Keycloak会话因闲置或达到最大时长过期时,refresh_token会失效,此时Next-Auth的JWT回调会触发刷新失败,session回调会标记会话过期。前端可以通过useSession监听会话状态,一旦发现过期,自动触发登出,而signOut回调会同步清除Keycloak端的会话。
3. 前端实现无活动超时监听
在前端监听用户的交互事件(鼠标移动、键盘输入等),如果3小时无活动,主动调用signOut同步两端会话:
// 示例:在_app.js中添加监听逻辑 import { useSession, signOut } from "next-auth/react"; import { useEffect } from "react"; function MyApp({ Component, pageProps }) { const { data: session } = useSession(); useEffect(() => { let timeoutId; // 重置超时器 const resetTimeout = () => { clearTimeout(timeoutId); // 3小时=10800000毫秒 timeoutId = setTimeout(() => { session && signOut({ callbackUrl: "/login" }); }, 10800000); }; // 绑定用户活动事件 window.addEventListener("mousemove", resetTimeout); window.addEventListener("keydown", resetTimeout); window.addEventListener("scroll", resetTimeout); // 初始化超时器 resetTimeout(); // 组件卸载时清除监听和超时器 return () => { clearTimeout(timeoutId); window.removeEventListener("mousemove", resetTimeout); window.removeEventListener("keydown", resetTimeout); window.removeEventListener("scroll", resetTimeout); }; }, [session]); return <Component {...pageProps} />; } export default MyApp;
三、关键注意事项
- Keycloak配置必须正确:将
Session Idle Timeout设为3小时,Session Max Lifetime设为1天,确保令牌有效期符合需求。 - 必须请求
offline_accessscope:否则Keycloak不会颁发可重复使用的refresh_token,刷新逻辑会失效。 - Next-Auth的
session.maxAge要和Keycloak的最大会话时长保持一致:避免Next-Auth会话还在但Keycloak会话已过期的情况。 - 前端监听是辅助,核心依赖Keycloak令牌刷新:即使前端没有触发无活动超时,当Keycloak会话过期后,Next-Auth刷新令牌失败也会自动同步登出。
内容的提问来源于stack exchange,提问作者Willem Nieuwoudt
相关产品推荐
相关产品推荐

