记录下Nuxtjs前端,nestjs服务端,Nginx做代理如何获取客户端IP

通常,我们要想在服务端获取到发起请求的客户端的IP,需要在两个地方做配置:

  1. Nginx 配置
  2. 服务端配置

Nginx 配置

下面是一个完整的通过域名访问服务的 Nginx 配置,配置了 https 证书。

服务端要获取客户端 IP,主要是依靠 location 块中的 X-Real-IpX-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,需要配置三个地方:

  1. 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;
       ...
    }
}
  1. 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';
        ...
    }
  1. Nuxt3 中 禁用获取数据接口的缓存,让每次刷新页面都去调用该接口

完结

感谢您的阅读

文章归类于: 码不停蹄

文章标签: #Nodejs#Nginx#项目

版权声明: 自由转载-署名-非商用