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

Docker Compose部署Next.js+Next-Auth+Keycloak遇连接拒绝错误

Docker Compose部署Next.js+Next-Auth+Keycloak时出现SIGNIN_OAUTH_ERROR

我基于Next.js和Next-Auth构建了React应用,使用Docker容器中的Keycloak做身份与访问管理。在容器外单独运行Next.js应用时一切正常,但通过Docker Compose与Keycloak一同部署时,出现连接拒绝错误。

错误日志

[next-auth][error][SIGNIN_OAUTH_ERROR]

https://next-auth.js.org/errors#signin_oauth_error connect ECONNREFUSED 127.0.0.1:8092 {
  error: {
    message: 'connect ECONNREFUSED 127.0.0.1:8092',
    stack: 'Error: connect ECONNREFUSED 127.0.0.1:8092\n' +
      '    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1606:16)\n' +
      '    at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17)',
    name: 'Error'
  },
  providerId: 'keycloak',
  message: 'connect ECONNREFUSED 127.0.0.1:8092'
}

Docker Compose配置

services:
  romford-web:
    image: myrepo:romoford-web-1.0
    restart: always
    container_name: romford-web
    environment:
      NEXT_PUBLIC_CUSTOMER_API_BASE_URL: http://romford-api:4010/api
      NEXT_PUBLIC_DEFAULT_PAGE_SIZE: 4
      NEXT_PUBLIC_DEFAULT_PAGE_NUMBER: 1
      NEXTAUTH_SECRET: secret
      NEXTAUTH_URL: http://localhost:3000
      NEXTAUTH_URL_INTERNAL: http://romford-web:3000
      KEYCLOAK_CLIENT_ID: next-auth-client
      KEYCLOAK_CLIENT_SECRET: secret-from-keycloack
      KEYCLOAK_ISSUER: http://host.docker.internal:8092/realms/romfordmotors
    ports:
      - '3000:3000'
    networks:
      - my_net

  romford-api:
    image: myrepo:romoford-api-1.0
    restart: always
    container_name: romford-api
    ports:
      - '4010:4010'
    networks:
      - my_net

  postgresql:
    image: postgres
    container_name: postgresql-dev
    restart: unless-stopped
    environment:
      POSTGRES_DB: bitnami_keycloak
      POSTGRES_USER: bn_keycloak
      POSTGRES_PASSWORD: password
    volumes:
      - 'postgresql_data:/var/lib/postgresql/data'
    networks:
      - my_net

  keycloak:
    image: docker.io/bitnami/keycloak:latest
    container_name: keycloak-dev
    restart: unless-stopped
    ports:
      - '8092:8080'
    environment:
      KEYCLOAK_CREATE_ADMIN_USER: true
      KEYCLOAK_DATABASE_PASSWORD: password
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin-password

    depends_on:
      - postgresql
    volumes:
      - 'keycloak_data:/opt/jboss/keycloak/standalone/data'
      - 'keycloak_config:/opt/jboss/keycloak/standalone/configuration'
    networks:
      - my_net

  mongodb:
    image: mongo
    restart: always
    container_name: mongodb
    ports:
      - '27018:27017'
    networks:
      - my_net
    volumes:
      - api-data:/data/db

volumes:
  keycloak_data:
  keycloak_config:
  api-data:
  postgresql_data:
    driver: local

networks:
  my_net:
    name: my_net

NextAuth Provider配置

import NextAuth, { Account, AuthOptions, Session, User } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import KeycloakProvider, {
  KeycloakProfile,
} from 'next-auth/providers/keycloak';

import bcrypt from 'bcrypt';
import { OAuthConfig } from 'next-auth/providers/oauth';
import { JWT } from 'next-auth/jwt';

export const authOptions = {
  providers: [
    KeycloakProvider({
      clientId: process.env.KEYCLOAK_CLIENT_ID as string,
      clientSecret: process.env.KEYCLOAK_CLIENT_SECRET as string,
      issuer: process.env.KEYCLOAK_ISSUER,
      name: 'Romford motors',
    }),
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials, req) {
        const userName = credentials?.username ?? '';
        const password = credentials?.password ?? '';
        const hash =
          '$2b$10$irGbj/bzy3bgDrfjw4adfQQOMke6tt1uiTf/D34BLeA2Lw7T7wMvADezzC';

        var compareResult = await bcrypt.compare(password, hash);
        const success = userName.toLowerCase() === 'sajid' && compareResult;

        const user = {
          id: '1',
          name: 'Saj Raj',
          email: 'saj@example.com',
          userName,
        };

        if (success) {
          return user;
        } else {
          return null;
        }
      },
    }),
  ],
  callbacks: {
    async jwt({ token, account }: { token: JWT; account: Account | null }) {
      if (account) {
        token.id_token = account.id_token;
        token.provider = account.provider;
        token.access_token = account.access_token;
      }
      return token;
    },
    async session({
      session,
      token,
      user,
    }: { session: Session; token: JWT; user: User }): Promise<Session> {
      session.accessToken = token.access_token;
      session.idToken = token.id_token;
      console.log(session);
      return session;
    },
  },
  events: {
    async signOut(message: { session: Session; token: JWT }) {
      if (message.token.provider === 'keycloak') {
        const issuerUrl = (
          authOptions.providers.find(
            (p) => p.id === 'keycloak'
          ) as OAuthConfig<KeycloakProfile>
        ).options!.issuer!;
        const logOutUrl = new URL(
          `${issuerUrl}/protocol/openid-connect/logout`
        );
        logOutUrl.searchParams.set('id_token_hint', message.token.id_token!);
        await fetch(logOutUrl);
      }
    },
  },
};

export default NextAuth(authOptions);

解决方案

问题根源

在Docker Compose的网络环境中,每个容器都是独立的网络节点,host.docker.internal或127.0.0.1只会指向容器自身,无法访问同一网络中的Keycloak容器。必须使用Keycloak容器的名称(keycloak-dev)和容器内部端口(8080)来建立连接。

修复步骤

  1. 修改romford-web的环境变量
    在Docker Compose文件中,将romford-web服务的KEYCLOAK_ISSUER改为:

    KEYCLOAK_ISSUER: http://keycloak-dev:8080/realms/romfordmotors
    

    这个地址是Docker内部网络中Keycloak的可访问地址。

  2. 配置Keycloak客户端的重定向与跨域
    登录Keycloak管理后台,找到next-auth-client客户端:

    • 在Valid Redirect URIs中添加http://localhost:3000/*(匹配用户浏览器访问的地址)
    • 在Web Origins中设置为http://localhost:3000,允许Next.js应用的跨域请求
  3. 验证登出逻辑
    由于signOut事件在Next.js服务器端执行,使用内部地址http://keycloak-dev:8080发起登出请求是可行的,无需额外修改代码。

完成以上修改后,重新启动Docker Compose服务,即可解决连接拒绝的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 13:09:51