next-auth集成KeycloakProvider:JWT字段缺失且签名无效
问题原因与解决方案
核心差异原因
- 浏览器存储的JWT是NextAuth自有Token:NextAuth默认会生成独立的Session Token存在浏览器中,并非直接转发Keycloak返回的原始ID Token。这个Token用NextAuth自身的密钥签名,而非Keycloak的密钥,所以在jwt.io用Keycloak公钥验证时会显示“Invalid Signature”,且长度更短(仅包含NextAuth默认保留的基础字段)。
- useSession返回字段不全:KeycloakProvider默认只从用户信息中提取
name和email,sub、roles等Keycloak特有字段不会自动被纳入,需要手动配置回调逻辑来获取。
具体解决步骤
1. 自定义KeycloakProvider的profile回调,提取目标字段
修改NextAuth配置,添加profile函数,从Keycloak返回的用户数据中抓取sub、roles等需要的字段:
import NextAuth from "next-auth"; import KeycloakProvider from "next-auth/providers/keycloak"; export const authOptions = { providers: [ KeycloakProvider({ clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, issuer: process.env.ISSUER, // 自定义profile回调,提取Keycloak返回的字段 profile(profile) { // Keycloak的角色通常存储在realm_access.roles下 const roles = profile.realm_access?.roles || []; return { id: profile.sub, // 对应sub字段 name: profile.name, email: profile.email, roles: roles, // 可按需添加其他Keycloak返回的字段 }; }, }) ], // 配置jwt与session回调,将自定义字段同步到前端可用的session中 callbacks: { async jwt({ token, user }) { // 用户首次登录时,将profile提取的字段存入JWT if (user) { token.id = user.id; token.roles = user.roles; } return token; }, async session({ session, token }) { // 将JWT中的字段同步到session,让useSession可以获取 session.user.id = token.id; session.user.roles = token.roles; return session; }, }, // 可选:配置NEXTAUTH_SECRET固定签名密钥,避免开发环境重启后密钥变化导致Token失效 secret: process.env.NEXTAUTH_SECRET, } export default NextAuth(authOptions);
2. 验证效果
重启应用并重新登录后,调用useSession()就能获取到包含id(对应sub)、roles等字段的完整用户信息。
如果需要直接获取Keycloak的原始ID Token,可以在jwt回调中额外保存:
async jwt({ token, user, account }) { if (user && account) { token.id = user.id; token.roles = user.roles; token.keycloakIdToken = account.id_token; // 保存Keycloak原始ID Token } return token; }
这个原始Token在jwt.io验证时就能显示完整数据且签名有效。
内容的提问来源于stack exchange,提问作者Alexander Schoch
相关产品推荐
相关产品推荐

