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)来建立连接。
修复步骤
修改romford-web的环境变量
在Docker Compose文件中,将romford-web服务的KEYCLOAK_ISSUER改为:KEYCLOAK_ISSUER: http://keycloak-dev:8080/realms/romfordmotors这个地址是Docker内部网络中Keycloak的可访问地址。
配置Keycloak客户端的重定向与跨域
登录Keycloak管理后台,找到next-auth-client客户端:- 在Valid Redirect URIs中添加
http://localhost:3000/*(匹配用户浏览器访问的地址) - 在Web Origins中设置为
http://localhost:3000,允许Next.js应用的跨域请求
- 在Valid Redirect URIs中添加
验证登出逻辑
由于signOut事件在Next.js服务器端执行,使用内部地址http://keycloak-dev:8080发起登出请求是可行的,无需额外修改代码。
完成以上修改后,重新启动Docker Compose服务,即可解决连接拒绝的问题。
内容的提问来源于stack exchange,提问作者iaq
相关产品推荐
相关产品推荐

