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

NestJS集成Swagger UI对接Keycloak OAuth时CORS跨域报错问题

NestJS Swagger 接入Keycloak OAuth2鉴权CORS报错排查方案

问题背景

  • 基于NestJS搭建的API服务已全量接入Keycloak鉴权,现有鉴权逻辑运行正常
  • 需求为通过@nestjs/swagger生成API文档,支持用户在Swagger UI端直接完成OAuth认证后调用接口,支持两种授权触发方式:打开Swagger UI时自动初始化登录、点击页面「Authorize」按钮手动授权
  • 已按照NestJS官方OpenAPI模块文档配置,且开启了CORS(甚至尝试过指定origin为https://localhost:3334等全量参数),仍持续出现CORS报错
    CORS报错截图

现有main.ts配置代码

import { NestFactory } from '@nestjs/core'
import { AppModule } from '@root/app.module'
import { DBService } from '@middleware/db.service'
import * as fs from 'fs'
import * as path from 'path'
import { Logger } from '@nestjs/common'
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'

async function bootstrap() {
  const ssl = process.env.SSL === 'true' ? true : false
  let httpsOptions = null
  if (ssl) {
    const keyPath = process.env.SSL_KEY_PATH || ''
    const certPath = process.env.SSL_CERT_PATH || ''
    httpsOptions = {
      key: fs.readFileSync(path.join(__dirname, keyPath), 'utf8'),
      cert: fs.readFileSync(path.join(__dirname, certPath), 'utf8')
    }
  }
  const app = await NestFactory.create(AppModule, { httpsOptions })
  app.enableCors()

  // swagger配置段
  const config = new DocumentBuilder()
    .addOAuth2(
      {
        type: 'oauth2',
        flows: {
          password: {
            tokenUrl: `${process.env.KEYCLOAK_AUTH_URL}/auth/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`,
            authorizationUrl: `${process.env.KEYCLOAK_AUTH_URL}/auth/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/auth`,
            scopes: {}
          }
        }
      })
    .setTitle('MyAPI')
    .setDescription('API description')
    .setVersion('0.1')
    .addTag('AM')
    .build()
  const document = SwaggerModule.createDocument(app, config)
  SwaggerModule.setup('api', app, document, {
    swaggerOptions: {
      oauth: {
        clientID: process.env.KEYCLOAK_CLIENT_ID,
        realm: process.env.KEYCLOAK_REALM,
        appName: 'swagger-ui'
      }
    }
  })


  const port = Number(process.env.PORT) || 3333
  const hostname = process.env.HOSTNAME || 'localhost'
  const dbService: DBService = app.get(DBService)
  dbService.enableShutdownHooks(app)
  await app.listen(port, hostname, () => {
    const address = 'http' + (ssl ? 's' : '') + '://' + hostname + ':' + port + '/'
    Logger.log('Listening at ' + address)
  });
}
bootstrap();

问题解答

1. CORS报错根本原因

你在NestJS服务中配置的app.enableCors()仅作用于发往NestJS服务自身的请求,本次报错的跨域请求是Swagger UI前端直接向Keycloak服务发起的,完全不经过NestJS服务,因此无论怎么调整NestJS侧的CORS配置都无法解决问题。
触发报错的核心原因分三类:

  • Keycloak侧客户端配置错误:用于Swagger对接的Keycloak客户端,未将Swagger UI的访问源、重定向地址加入白名单,Keycloak收到跨域请求时返回的响应不带合法CORS头,被浏览器拦截。
  • 授权模式选择错误:当前配置使用password密码模式,该模式不支持浏览器端跳转授权流程,Swagger UI会直接从前端发起跨域令牌请求,Keycloak默认拒绝该类跨域请求。
  • 端点路径错误:17及以上版本的Keycloak(基于Quarkus发行)默认移除了接口路径的/auth前缀,若你使用新版Keycloak却保留了/auth前缀,请求会直接返回404,404响应自然不会携带CORS头,同样会触发跨域报错。

2. 当前Swagger OAuth配置合规性判断

现有配置不符合对接规范,存在3个明确问题:

  • 授权流选型错误:浏览器端Swagger UI对接Keycloak必须使用authorizationCode授权码模式,password模式已被OAuth2.1标记为废弃,且不适合纯前端场景使用。
  • OAuth初始化参数缺失:Swagger的oauth配置未开启PKCE校验(Keycloak公共客户端默认强制要求PKCE)、未携带必填的openid scope(Keycloak基于OIDC协议,必须携带该scope才能完成认证),也未配置页面加载自动授权的相关参数。
  • 未配置全局安全要求:仅添加了OAuth2安全定义,但未调用.addSecurityRequirements('oauth2')将安全规则应用到所有接口,即使授权成功,调用接口时Swagger也不会自动携带令牌。

3. 正确配置步骤

按以下顺序调整即可实现需求:

  1. 调整Keycloak客户端配置
    • 将客户端访问类型设置为public(公共客户端,无需在前端暴露客户端密钥)
    • 「有效重定向URI」中添加Swagger的回调地址,格式为你的Swagger访问域名/oauth2-redirect.html,例如https://localhost:3333/api/oauth2-redirect.html
    • 「Web源」配置中添加Swagger的访问源,例如https://localhost:3333
    • 客户端认证流仅开启「标准流」(即授权码流),关闭其余不需要的流
  2. 替换原有Swagger配置代码
// 请根据自身Keycloak版本调整路径前缀:17+版本去掉路径中的/auth段
const keycloakBase = process.env.KEYCLOAK_AUTH_URL
const keycloakRealm = process.env.KEYCLOAK_REALM
const keycloakClient = process.env.KEYCLOAK_CLIENT_ID
const serviceBaseUrl = `http${ssl ? 's' : ''}://${hostname}:${port}`

const config = new DocumentBuilder()
  .addOAuth2({
    type: 'oauth2',
    flows: {
      authorizationCode: {
        authorizationUrl: `${keycloakBase}/realms/${keycloakRealm}/protocol/openid-connect/auth`,
        tokenUrl: `${keycloakBase}/realms/${keycloakRealm}/protocol/openid-connect/token`,
        scopes: {
          openid: 'OIDC基础权限'
        }
      }
    }
  })
  .setTitle('MyAPI')
  .setDescription('API description')
  .setVersion('0.1')
  .addTag('AM')
  // 全局应用OAuth2鉴权规则
  .addSecurityRequirements('oauth2')
  .build()

const document = SwaggerModule.createDocument(app, config)
SwaggerModule.setup('api', app, document, {
  swaggerOptions: {
    oauth: {
      clientId: keycloakClient,
      appName: 'swagger-ui',
      usePkceWithAuthorizationCodeGrant: true,
      scopes: ['openid']
    },
    persistAuthorization: true,
    oauth2RedirectUrl: `${serviceBaseUrl}/api/oauth2-redirect.html`,
    // 打开Swagger页面时自动触发授权登录
    initOAuth: true
  }
})
  1. 若本地部署使用自签名SSL证书,需提前在浏览器中信任Keycloak和NestJS服务的证书,证书校验失败也会被浏览器归类为跨域类报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 12:48:18