NestJS Swagger UI 在 Android 真机上加载正常,为何请求报 CORS 错误?
先检查 Swagger 实际请求的地址
先看 Swagger 的 servers 配置:
.addServer('http://localhost:3000', 'Local Development')
.addServer('http://192.168.1.5:3000', 'Local Network')
如果 Swagger UI 当前选中的是 http://localhost:3000,手机就会向自身的 3000 端口发送请求。手机上的 localhost 指向手机,不会指向 MacBook,因此修改 MacBook 上的 CORS 配置也无法解决这个地址错误。
这次测试可以让文档页面和 API 都使用 http://192.168.1.5:3000。两者同源时,请求不需要跨域授权。报错中的 Possible reasons: CORS, Network failure... 只是通用提示,单凭这段信息无法确定问题出在 CORS。
为什么文档能打开,API 请求却失败?
加载 Swagger 页面和点击 Try it out 时,浏览器使用的地址可能不同:
- 文档页面使用浏览器地址栏中的 URL。
- API 请求使用 Swagger UI 当前选择的 server 地址,再拼接接口路径。
打开:
http://192.168.1.5:3000/api/docs
并不代表 /auth/login 一定会请求:
http://192.168.1.5:3000/auth/login
它也可能请求:
http://localhost:3000/auth/login
文档能打开,说明手机能够访问 MacBook 的这个地址和端口。Swagger 生成的 API 请求是否使用了相同地址,还需要单独确认。
协议、主机和端口都相同才算同源,路径不参与判断:
| 页面地址 | API 地址 | 是否同源 |
|---|---|---|
http://192.168.1.5:3000/api/docs | http://192.168.1.5:3000/auth/login | 是 |
http://192.168.1.5:3000/api/docs | http://localhost:3000/auth/login | 否,且目标是手机自身 |
http://192.168.1.5:5175 | http://192.168.1.5:3000/auth/login | 否,端口不同 |
Android Emulator 中的 10.0.2.2 是访问宿主机回环接口的特殊地址。Android 真机没有这层映射,应使用 MacBook 的局域网 IP。
第一步:修正 Swagger 的 server 地址
在 Swagger UI 的 Servers 下拉框中选择:
http://192.168.1.5:3000
再查看请求区域的 Request URL,确认请求指向 MacBook。
如果文档和 API 始终使用同一个地址,可以使用相对 server URL:
const swaggerConfig = new DocumentBuilder()
.setTitle('Liaison360 API')
.addServer('/', 'Current origin')
.build();
这样,用手机访问局域网地址时,API 请求会使用该地址;在 MacBook 上通过 localhost 访问时,请求也会跟随当前页面的来源。
这里假设 API 位于站点根路径下,例如 /auth/login。如果配置了全局前缀或反向代理路径,需要检查生成的 OpenAPI paths 和最终的 Request URL,避免路径前缀重复或缺失。
也可以只保留局域网地址,但需要保证 MacBook 的 IP 不变:
const swaggerConfig = new DocumentBuilder()
.setTitle('Liaison360 API')
.addServer('http://192.168.1.5:3000', 'Local Network')
.build();
第二步:按实际页面来源配置 CORS
origin 白名单应填写发起请求的页面来源,而不是 API 的目标地址。
当前配置:
origin: ['http://localhost:5175']
允许通过 http://localhost:5175 打开的 Vite 页面跨域访问后端。如果手机通过 http://192.168.1.5:5175 打开 Vite 页面,就需要把这个来源也加入白名单。
可以使用下面的配置,支持电脑和手机上的 Vite 页面,同时让 Swagger 请求跟随当前页面的来源:
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: [
'http://localhost:5175',
'http://192.168.1.5:5175',
],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
});
const swaggerConfig = new DocumentBuilder()
.setTitle('Liaison360 API')
.addServer('/', 'Current origin')
.build();
const document = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup('api/docs', app, document);
await app.listen(3000, '0.0.0.0');
}
bootstrap();
只有通过 http://192.168.1.5:5175 访问 Vite 时,才需要允许这个来源。Vite 开发服务器本身也必须允许局域网访问。
如果 Swagger 页面和 API 都在 http://192.168.1.5:3000,就不必为了 Swagger 把这个来源加入白名单。如果 Swagger 需要跨源调用另一个 API,应在目标 API 的 CORS 配置中允许 Swagger 页面的来源,例如:
origin: [
'http://localhost:5175',
'http://192.168.1.5:5175',
'http://192.168.1.5:3000',
]
来源只包含协议、主机和端口,不要带上路径,例如 http://192.168.1.5:3000/api/docs。
修改后如何确认问题所在?
重启后端并刷新 Swagger 页面,按以下顺序检查:
- 查看
Servers和Request URL,确认请求指向192.168.1.5:3000。 - 使用 Android Chrome 远程调试,查看 Network 和 Console 中的具体错误。
- 如果有跨域
OPTIONS预检请求,检查响应中的Access-Control-Allow-Origin、Access-Control-Allow-Methods和Access-Control-Allow-Headers。 - 如果请求发生重定向,检查重定向后的地址,尤其要留意是否跳到了
localhost、其他端口或不同协议。
跨域发送 Content-Type: application/json 或 Authorization 时,通常会触发预检。需要确保预检请求能够通过,前置代理或鉴权逻辑不能将其拦截。
如果最终请求始终与文档同源,就继续排查连接失败、重定向,以及浏览器控制台报告的其他限制。普通的 401、404、500 响应本身通常不会让 fetch 以网络错误的形式失败。
credentials: true 的注意事项
credentials: true 表示服务端允许携带凭据的跨域请求。它无法修复请求地址错误,也不会自动让客户端发送 Cookie。
如果使用跨域 Cookie:
Access-Control-Allow-Origin必须返回明确的来源,不能使用*。- 客户端也需要启用凭据发送;Swagger UI 可按需配置
swaggerOptions: { withCredentials: true }。 - Cookie 是否发送还受
SameSite、Secure等属性影响。
如果只是通过 Authorization: Bearer ... 传递令牌,通常不需要为此开启 credentials: true,但跨域预检仍需允许 Authorization 请求头。
备注:内容仅供参考。
