通常,我们要想在服务端获取到发起请求的客户端的IP,需要在两个地方做配置:
- Nginx 配置
- 服务端配置
Nginx 配置
下面是一个完整的通过域名访问服务的 Nginx
配置,配置了 https 证书。
服务端要获取客户端 IP
,主要是依靠 location
块中的 X-Real-Ip
和 X-Forwarded-For
两行配置。
将客户端的真实 IP 地址(由 Nginx 接收到的客户端请求 IP 地址)设置为 X-Real-IP 的值,并通过请求头传递给后端服务器。
$remote_addr
是 Nginx 内置变量,表示发起请求的客户端 IP 地址。
$proxy_add_x_forwarded_for
表示当前请求的 X-Forwarded-For 头的值,并在末尾追加 $remote_addr(当前客户端 IP 地址)
一句话总结:通过 X-Real-Ip
获取真实 IP,通过 X-Forwarded-For
获取经过的代理链的 ip
server{
server_name xxx.brandhuang.com;
location /frontend/ {
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:XXX/;
}
location /backend/ {
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:XXX/;
}
listen 443 ssl; # managed by Certbot
listen [::]:443 ssl;
ssl_certificate /etc/letsencrypt/live/xxx.brandhuang.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/xxx.brandhuang.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
通常 Nginx 经过上面两个配置就可以了。
服务端配置
即,在
Nestjs
代码中配置
首先需要在入口文件(通常是 main.js
)中开启 trust proxy
,代码如下
重要是的这两部分:
NestExpressApplication
app.set('trust proxy', 'loopback');
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
cors: ENV === "production" ? {
origin: ['https://admintest.brandhuang.com', 'https://admin.brandhuang.com'],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
} : true,
// cors: true,
});
...
app.set('trust proxy', 'loopback');
...
await app.listen(process.env.ADMIN_PORT);
console.log(`backend service is running at http://localhost:${process.env.ADMIN_PORT}`);
}
bootstrap();
然后在接口中获取 ip
@Controller('visit')
export class IpController {
constructor(
private readonly ipService: IpService,
) {
}
@Post('/setVisitInfo')
@ApiOperation({
summary: '获取客户端访问信息',
})
@HttpCode(200)
async getVisitInfo(@Req() req): Promise<any> {
const ip = req.ip || req.headers['x-forwarded-for']?.toString() || 'Unknown IP';
return await this.ipService.setIpInfo({ip});
}
}
这样,两项配置就完成了
但是,我经过前面的配置后,访问 nuxt3 构建的SSR项目时,很多时候拿到的都是接口部署的服务器的IP
首先怀疑的就是,我的 Nginx 是不是配置出错了?搜了几天资料,都是这么配置。最后通过在 chatGPT 中不断提问,它告诉我去分析 Nginx 的日志
最后让 GPT 分析了一部分Nginx日志后发现,有一些请求时通过 node 发起的,而不是通过浏览器发起的。所以只能拿到部署node服务的服务器ip
经过一番排查,nuxt3 内置的数据请求 hooks :useFetch
会默认对请求进行缓存,当我们访问过一个页面后,再次刷新该页面时,只会在服务端发起请求,客户端不会发起请求。所以我们需要修改 获取客户端信息接口的请求方式:禁用缓存
// 通过 配置 server: false
// 即可让接口每次刷新页面都在客户端发起请求
// 这样就能拿到客户端ip了
await useFetch(baseURL + url, {
method: method,
server: cache,
// body: params ? JSON.stringify(params) : null,
onRequest: ({ request, options }) => {
options.body = params
},
onResponse: ({ response }) => {
// console.log("response=", response._data)
if (response?._data?.code !== 200) {
throw createError({ statusCode: response?._data.code, statusMessage: response?._data.message })
}
return response;
},
onRequestError({ request, options, error }) {
// 处理请求错误
// console.warn('request error', error);
// showToast('Request Error');
},
onResponseError({ request, response, options }) {
// 处理响应错误
// console.warn('response error', response);
throw createError({ statusCode: response.status, statusMessage: response.statusText })
// showToast('Request Error');
},
})
总结
基于 nestjs、Nginx、Nuxt3 项目,要获取客户端ip,需要配置三个地方:
- Nginx配置
server {
...
server_name xxx.xxx.com
...
location / {
...
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
...
}
}
- nestjs 配置
// 在 main.js 中
import { NestExpressApplication } from '@nestjs/platform-express';
...
const app = await NestFactory.create<NestExpressApplication>(AppModule)
...
app.set('trust proxy', 'loopback');
...
// 在接口 controller 中
async getVisitInfo(@Req() req): Promise<any> {
const ip = req.ip || req.headers['x-forwarded-for']?.toString() || 'Unknown IP';
...
}
- Nuxt3 中 禁用获取数据接口的缓存,让每次刷新页面都去调用该接口
完结
感谢您的阅读