FastAPI (starlette) 获取客户端真实IP

FastAPI (starlette) get client real IP

我在 FastAPI 上有一个 API,当他请求我的页面时,我需要获取客户端的真实 IP 地址。

我很想使用 starlette 请求。但它 returns 我的服务器 IP,而不是客户端远程 IP。

我的代码:

@app.post('/my-endpoint')
async def my_endpoint(stats: Stats, request: Request):
    ip = request.client.host
    print(ip)
    return {'status': 1, 'message': 'ok'}

我做错了什么?如何获取真实 IP(如 Flask request.remote_addr)?

request.client 应该可以工作,除非你 运行 在代理(例如 nginx)后面,在这种情况下使用 uvicorn 的 --proxy-headers 标志来接受这些传入的 headers 和确保代理转发它们。

如果你使用nginx和uvicorn,你应该为uvicorn设置proxy-headers,你的nginx配置应该添加HostX-Real-IPX-Forwarded-For
例如

server {
  # the port your site will be served on
    listen 80;
  # the domain name it will serve for
    server_name <your_host_name>; # substitute your machine's IP address or FQDN

#    add_header Access-Control-Allow-Origin *;
    # add_header Access-Control-Allow-Credentials: true;
    add_header Access-Control-Allow-Headers Content-Type,XFILENAME,XFILECATEGORY,XFILESIZE;
    add_header access-control-allow-headers authorization;
    # Finally, send all non-media requests to the Django server.
    location / {
        proxy_pass http://127.0.0.1:8000/; # the uvicorn server address
        proxy_set_header   Host             $host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    }
}

关于 nginx 文档:

This middleware can be applied to add HTTP proxy support to an
application that was not designed with HTTP proxies in mind. It
sets REMOTE_ADDR, HTTP_HOST from X-Forwarded headers. While
Werkzeug-based applications already can use
:py:func:werkzeug.wsgi.get_host to retrieve the current host even if
behind proxy setups, this middleware can be used for applications which
access the WSGI environment directly。
If you have more than one proxy server in front of your app, set
num_proxies accordingly.
Do not use this middleware in non-proxy setups for security reasons.
The original values of REMOTE_ADDR and HTTP_HOST are stored in
the WSGI environment as werkzeug.proxy_fix.orig_remote_addr and
werkzeug.proxy_fix.orig_http_host
:param app: the WSGI application
:param num_proxies: the number of proxy servers in front of the app.  

如果你已经根据@AllenRen的回答正确配置了你的nginx配置, 尝试对 uvicorn 使用 --proxy-headers--forwarded-allow-ips='*' 标志。

FastAPI using-request-directly 文档页面显示了这个例子:

from fastapi import FastAPI, Request

app = FastAPI()


@app.get("/items/{item_id}")
def read_root(item_id: str, request: Request):
    client_host = request.client.host
    return {"client_host": client_host, "item_id": item_id}

如果有这个例子,我可以节省十分钟的时间 Starlette's Request class

您将使用以下代码从客户端获取真实 IP 地址。如果您使用反向代理和端口转发

@app.post('/my-endpoint')
async def my_endpoint(stats: Stats, request: Request):
    x = 'x-forwarded-for'.encode('utf-8')
    for header in request.headers.raw:
        if header[0] == x:
            print("Find out the forwarded-for ip address")
            origin_ip, forward_ip = re.split(', ', header[1].decode('utf-8'))
            print(f"origin_ip:\t{origin_ip}")
            print(f"forward_ip:\t{forward_ip}")
    return {'status': 1, 'message': 'ok'}

您不需要设置 --proxy-headers,因为它默认启用,但它只信任来自 --forwarded-allow-ips 的 IP,默认为 127.0.0.1

为了安全起见,您应该只信任来自反向代理 IP 的代理 headers(而不是信任所有 '*')。如果它在同一台机器上,那么默认值应该可以工作。尽管我从我的 nginx 日志中注意到它使用 ip6 与 uvicorn 通信,所以我不得不使用 --forwarded-allow-ips='[::1]' 然后我可以在 FastAPI 中看到 ip 地址。您还可以使用 --forwarded-allow-ips='127.0.0.1,[::1]' 在本地主机上捕获 ip4 和 ip6。

--proxy-headers / --no-proxy-headers - Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info. Defaults to enabled, but is restricted to only trusting connecting IPs in the forwarded-allow-ips configuration.

--forwarded-allow-ips - Comma separated list of IPs to trust with proxy headers. Defaults to the $FORWARDED_ALLOW_IPS environment variable if available, or '127.0.0.1'. A wildcard '*' means always trust.

参考:https://www.uvicorn.org/settings/#http

我已使用 docker-compose 文件进行部署,更改为

nginx。配置文件

 location / {
  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_pass http://localhost:8000;
}

Dockerfile 的变化

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]

docker-compose.yaml 文件中的更改

version: "3.7"
services:
  app:
    build: ./fastapi
    container_name: ipinfo
    restart: always
    ports:
      - "8000:8000"
    network_mode: host

  nginx:
    build: ./nginx
    container_name: nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    network_mode: host

这些更改后正确获得客户端外部 IP

分享在stand-aloneubuntu-basedweb-serverinstance/droplet(亚马逊EC2 / DigitalOcean / Hetzner / SSDnodes)。长话短说:使用 X_Forwarded_For
我假设您已注册域名并将服务器固定到该域名。

代码中

from fastapi import FastAPI, Header

app = FastAPI()

@app.get("/API/path1")
def path1(X_Forwarded_For: Optional[str] = Header(None)):
    print("X_Forwarded_For:",X_Forwarded_For)
    return { "X_Forwarded_For":X_Forwarded_For }

当 运行 在本地机器上并点击 localhost:port/API/path1 时,这给出了一个空值,但在我部署的站点中,当我点击 API 时,它正确地给出了我的 IP 地址。

在程序启动命令中

uvicorn launch1:app --port 5010 --host 0.0.0.0 --root-path /site1

主程序在launch1.py。请注意此处的 --root-path arg - 如果您的应用程序不在 URL.
的根级别部署,这一点很重要 这会处理 url 映射,因此在上面的程序代码中我们不需要将它包含在 @app.get 行中。使程序可移植 - 明天您可以将它从 /site1 移动到 /site2 路径,而无需编辑代码。

在服务器设置中

我的 web-server 上的设置:

  • Apache 服务器已设置
  • LetsEncrypt SSL 已启用
  • 编辑/etc/apache2/sites-available/[站点名称]-le-ssl.conf
  • 标签内添加这些行:
    ProxyPreserveHost On

    ProxyPass /site1/ http://127.0.0.1:5010/
    ProxyPassReverse /site1/ http://127.0.0.1:5010/
  • 启用proxy_http并重启Apache
a2enmod proxy_http
systemctl restart apache2

一些很好的服务器设置指南:

通过所有这些设置,您可以在 https://[sitename]/site1/API/path1 上访问您的 api 端点,并且应该在响应中看到与您在 [= 上看到的相同的 IP 地址19=].

我有 docker-compose 和 nginx 代理。以下帮助:

  1. 在forwarded-allow-ips中指定'*'(docker-compose.yml文件中的环境变量)

- FORWARDED_ALLOW_IPS=*

  1. 按照@allenren
  2. 的建议将代码添加到nginx.conf文件
location /api/ {
    proxy_pass http://backend:8000/;
    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
}