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

Next.js+NextAuth+Keycloak集成无法获取JWT令牌对接Quarkus后端问题

问题分析与解决方案

疑问解答

  1. 这类问题非常常见,NextAuth默认不会主动暴露Keycloak颁发的原始JWT,而是生成自身的会话令牌,这是导致你无法获取Keycloak JWT的核心原因之一。
  2. 完全可行且是业界常见架构:Keycloak作为统一身份提供商(IDP),NextAuth负责前端认证流程,Quarkus后端通过验证Keycloak JWT实现接口鉴权,这套组合能很好支撑前后端分离的安全架构。
  3. 关键在于调整NextAuth配置以捕获并传递Keycloak JWT,同时确保前后端的令牌验证逻辑对齐,具体步骤如下:

步骤1:修改NextAuth配置以获取Keycloak JWT

在pages/api/auth/[...nextauth].js(或app/api/auth/[...nextauth]/route.js,对应Next.js 13+ App Router)中调整配置,重点关注scope设置和回调函数:

import NextAuth from "next-auth";
import KeycloakProvider from "next-auth/providers/keycloak";

export const authOptions = {
  secret: process.env.NEXTAUTH_SECRET, // 必须设置,从环境变量读取
  providers: [
    KeycloakProvider({
      clientId: process.env.KEYCLOAK_CLIENT_ID,
      clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
      issuer: process.env.KEYCLOAK_ISSUER, // 格式:https://your-keycloak-domain/realms/your-realm
      authorization: {
        params: {
          scope: "openid email profile offline_access", // 必须包含openid才能获取id_token
        },
      },
    }),
  ],
  // 自定义JWT回调,保存Keycloak的原始令牌
  callbacks: {
    async jwt({ token, account }) {
      // 用户首次登录时,保存Keycloak的access_token和id_token
      if (account) {
        token.accessToken = account.access_token;
        token.idToken = account.id_token;
        token.refreshToken = account.refresh_token;
        token.expiresAt = account.expires_at * 1000; // 转换为毫秒
      }
      // 检查令牌是否即将过期,可在此添加刷新逻辑
      if (Date.now() < token.expiresAt) {
        return token;
      }
      // 令牌过期时刷新(可选,需实现刷新逻辑)
      return await refreshAccessToken(token);
    },
    // 自定义Session回调,将Keycloak令牌暴露给前端
    async session({ session, token }) {
      session.user.accessToken = token.accessToken;
      session.user.idToken = token.idToken;
      return session;
    },
  },
};

// 可选:实现令牌刷新逻辑
async function refreshAccessToken(token) {
  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,
      idToken: refreshedTokens.id_token,
      refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,
      expiresAt: Date.now() + refreshedTokens.expires_in * 1000,
    };
  } catch (error) {
    return {
      ...token,
      error: "RefreshAccessTokenError",
    };
  }
}

export default NextAuth(authOptions);

步骤2:前端传递JWT到Quarkus后端

在前端API请求中,从NextAuth的session中获取accessToken,并添加到请求头的Authorization字段:

import { useSession } from "next-auth/react";

async function fetchProtectedData() {
  const { data: session } = useSession();
  if (!session?.user?.accessToken) {
    throw new Error("用户未登录");
  }

  const response = await fetch("https://your-quarkus-backend/api/protected", {
    headers: {
      Authorization: `Bearer ${session.user.accessToken}`,
    },
  });

  if (!response.ok) {
    throw new Error("请求失败");
  }
  return response.json();
}

步骤3:验证Quarkus后端配置

确保Quarkus的application.properties正确配置Keycloak OIDC验证:

# Keycloak OIDC配置
quarkus.oidc.auth-server-url=https://your-keycloak-domain/realms/your-realm
quarkus.oidc.client-id=your-keycloak-client-id
quarkus.oidc.credentials.secret=your-keycloak-client-secret # 若客户端为confidential类型需设置

# 保护API端点
quarkus.http.auth.permission.authenticated.paths=/api/**
quarkus.http.auth.permission.authenticated.policy=authenticated

关键注意事项

  • Keycloak客户端配置:确保客户端的Access Type与NextAuth匹配:若NextAuth运行在服务器端,建议设为confidential;若纯前端部署,设为public。同时开启Direct Access Grants Enabled(可选,用于令牌刷新)。
  • Scope必须包含openid:OIDC协议要求openid scope才能颁发id_token和access_token,这是很多开发者容易遗漏的点。
  • NextAuth Secret:必须设置NEXTAUTH_SECRET环境变量,否则会话加密会出问题,导致令牌无法正确存储和传递。

内容的提问来源于stack exchange,提问作者Rekouwn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 06:27:47