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

Docker环境下NestJS集成Keycloak的Token验证错误排查

Docker环境下NestJS集成Keycloak的认证问题

环境配置

Dockerfile

FROM node:18-alpine

WORKDIR /app

COPY package.json ./
COPY package-lock.json ./

RUN npm install

COPY . .

RUN npx prisma generate
RUN npm run build

EXPOSE 3000

CMD ["node", "dist/main.js"]

docker-compose.yml

version: '3.8'
services:
  nest-app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/mydatabase"
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - my-network

  postgres:
    image: postgres:latest
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mydatabase
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - my-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  keycloak:
    image: quay.io/keycloak/keycloak:latest
    environment:
      DB_VENDOR: h2
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
      KEYCLOAK_IMPORT: /tmp/realm.json
    ports:
      - "8081:8080"
    volumes:
      - ./realm.json:/tmp/realm.json
    entrypoint: ["/opt/keycloak/bin/kc.sh"]
    command: ["start-dev"]
    networks:
      - my-network

volumes:
  postgres_data:

networks:
  my-network:
    driver: bridge

app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import {
  AuthGuard,
  KeycloakConnectModule,
  ResourceGuard,
  RoleGuard,
} from 'nest-keycloak-connect';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaModule } from './prisma.module';
import { APP_GUARD } from '@nestjs/core';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    PrismaModule,
    KeycloakConnectModule.register({
      authServerUrl: 'http://127.0.0.1:8081/',
      realm: 'test',
      clientId: 'my-nest-project',
      secret: 'GSLUjhMMctvip6B01q3g7xu6P8SJBvT6', // or `credentials.secret` if used
    }),
  ],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_GUARD,
      useClass: AuthGuard,
    },
    {
      provide: APP_GUARD,
      useClass: ResourceGuard,
    },
    {
      provide: APP_GUARD,
      useClass: RoleGuard,
    },
  ],
})
export class AppModule {}

app.service.ts

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello, World!';
  }

  getProtectedMessage(): string {
    return 'This is a protected route';
  }

  getTestMessage(): string {
    return 'This is a test route';
  }
}

main.ts

import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { PrismaService } from './prisma.service';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Swagger configuration
  const config = new DocumentBuilder()
    .setTitle('API Documentation')
    .setDescription('The API description')
    .setVersion('1.0')
    .addBearerAuth()
    .addTag('app')
    .addTag('hello')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  const prismaService = app.get(PrismaService);
  await prismaService.enableShutdownHooks(app);

  await app.listen(3000);
}
bootstrap();

问题现象

Keycloak日志出现错误:

WARN [Keycloak] Cannot validate access token: Error: Grant validation failed. Reason: invalid token (wrong ISS)

API调用情况

  • 公开端点:
    • GET http://localhost:3000/(正常)
    • GET http://localhost:3000/test(正常)
    • GET http://localhost:3000/hello(正常)
    • GET http://localhost:8081/realms/test(正常)
  • 获取Token:POST http://localhost:8081/realms/test/protocol/openid-connect/token(正常)
  • 访问受保护路由:GET http://localhost:3000/protected(返回401 Unauthorized)

已尝试的解决方法

  • 验证Token有效性:通过Keycloak管理界面获取Token访问受保护路由,仍返回401
  • 调整authServerUrl:测试http://127.0.0.1:8081/、http://127.0.0.1:8081/auth、http://localhost:8081/auth等配置,问题依旧
  • 重建Keycloak Realm:重新创建后错误仍存在
  • 搜索相关问题:未找到有效解决方案

求助问题

  1. 可以检查或调整哪些配置解决此错误?
  2. Docker或NestJS配置中是否需要额外设置确保与Keycloak正确集成?

解决方案

核心原因

wrong ISS错误本质是Token中的签发者(iss)地址与NestJS验证时使用的地址不匹配:

  • 从宿主机访问Keycloak获取Token时,Token的iss字段是http://localhost:8081/realms/test
  • NestJS运行在Docker容器中,配置的authServerUrl是http://127.0.0.1:8081/,但容器内的127.0.0.1指向自身而非宿主机的Keycloak服务,导致验证时推导的iss与Token中的不一致。

具体修复步骤

1. 调整Keycloak的前端URL配置

在docker-compose.yml的keycloak服务中添加环境变量,强制Token的iss使用宿主机可访问的地址:

keycloak:
  image: quay.io/keycloak/keycloak:latest
  environment:
    DB_VENDOR: h2
    KEYCLOAK_ADMIN: admin
    KEYCLOAK_ADMIN_PASSWORD: admin
    KEYCLOAK_IMPORT: /tmp/realm.json
    # 添加这一行
    KEYCLOAK_FRONTEND_URL: http://localhost:8081/
  ports:
    - "8081:8080"
  volumes:
    - ./realm.json:/tmp/realm.json
  entrypoint: ["/opt/keycloak/bin/kc.sh"]
  command: ["start-dev"]
  networks:
    - my-network

2. 修改NestJS的Keycloak配置

让NestJS容器能正确访问Keycloak服务,同时指定匹配的issuer:
修改app.module.ts中的KeycloakConnectModule.register配置:

KeycloakConnectModule.register({
  // 用Docker网络内的服务名访问Keycloak,容器内部端口是8080
  authServerUrl: 'http://keycloak:8080/',
  realm: 'test',
  clientId: 'my-nest-project',
  secret: 'GSLUjhMMctvip6B01q3g7xu6P8SJBvT6',
  // 手动指定与Token一致的issuer地址
  issuer: 'http://localhost:8081/realms/test',
}),

3. 重启服务

执行以下命令重启所有服务,让配置生效:

docker-compose down
docker-compose up --build

额外检查项

  • 确认realm.json中的realm配置里,issuer字段是否与KEYCLOAK_FRONTEND_URL一致,如果不一致,修改realm.json后重新导入
  • 检查Keycloak客户端my-nest-project的配置:确保Access Type为confidential(因为你使用了secret),并且Valid Redirect URIs/Web Origins设置正确(测试环境可暂时设为*)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 07:50:55