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

Posted

技术标签:

【中文标题】FastAPI (starlette) 获取客户端真实IP【英文标题】:FastAPI (starlette) get client real IP 【发布时间】:2020-05-22 16:17:13 【问题描述】:

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

我很乐意使用 starlette 请求。但它返回的是我的服务器 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 中)?

【问题讨论】:

【参考方案1】:

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

【讨论】:

【参考方案2】:

如果你使用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.  

【讨论】:

上游 uvicorn 服务器 unix:/tmp/uvicorn.sock;我们不添加这个吗?它在文档中【参考方案3】:

FastAPI using-request-directly doc 页面显示了这个示例:

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

【讨论】:

【参考方案4】:

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

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

--proxy-headers / --no-proxy-headers - 启用/禁用 X-Forwarded-Proto、X-Forwarded-For、X-Forwarded-Port 以填充远程地址信息。默认启用,但仅限于在 forwarded-allow-ips 配置中信任连接 IP。

--forwarded-allow-ips - 以逗号分隔的 IP 列表以信任代理标头。默认为 $FORWARDED_ALLOW_IPS 环境变量(如果可用)或“127.0.0.1”。通配符“*”表示始终信任。

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

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

您将使用以下代码从客户端获取真实 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:\torigin_ip")
            print(f"forward_ip:\tforward_ip")
    return 'status': 1, 'message': 'ok'

【讨论】:

【参考方案7】:

我已经部署了 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 之后

【讨论】:

以上是关于FastAPI (starlette) 获取客户端真实IP的主要内容,如果未能解决你的问题,请参考以下文章

FastAPI Web框架 [Pydantic]

FastAPI Web框架 [Pydantic]

FastAPI Web框架 [Pydantic]

fastapi坑--fastapi 书写EventSourceResponse 迭代器的时候一定要加sleep

三万字长文让你彻底掌握 FastAPI

FastAPI 源码阅读 (五) 其余主体内容