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

Nat*_*ova 13 python x-forwarded-for starlette fastapi

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

我很乐意使用starlette Request。但它返回我的服务器 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'}
Run Code Online (Sandbox Code Playgroud)

我在做什么错?如何获得真实IP(如在Flask request.remote_addr 中)?

har*_*lee 32

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}
Run Code Online (Sandbox Code Playgroud)

有了这个例子,我就可以节省十分钟的时间来思考Starlette 的 Request 类

  • @Houman,你没有。IP4 和 IP6 是完全独立的,如果通过 IPv6 建立连接,则无法知道相应的 IP4 地址。事实上,原则上客户端甚至可能没有 IP4 地址。用一个老式的比喻来说,这就像询问如何从某人那里获得语音呼叫的电话号码(如果您收到某人的传真)。 (5认同)
  • 就我而言,它只显示我的 IPv6。但如何同时获得 IPv4 和 IPv6 呢? (2认同)
  • @Houman,并不总是如此。就我而言,在代理后面,我只能看到 IP4 地址。需要意识到的是,这样的服务可以在使用多个连接的情况下应用复杂的技巧。但如果您只有一个(例如 FastAPI),则只有一个 IP 地址适用。回到传真的类比,我确信找到您拥有传真号码的公司的电话号码是相当容易的。但这并不意味着传真和电话总是有着千丝万缕的联系 (2认同)

Hed*_*ide 19

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

  • 这是正确的,但也请注意@RcoderNY的评论,我想用此处描述的案例来确认这个答案https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/224#issuecomment-1429593840 (2认同)

Rco*_*rNY 16

你不需要设置--proxy-headersbc 它默认是启用的,但它只信任--forwarded-allow-ips默认的IP127.0.0.1

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

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

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

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


小智 11

如果您使用 nginx 和 uvicorn\xef\xbc\x8c,则应设置proxy-headersuvicorn\xef\xbc\x8c,并且您的 nginx 配置应添加Host\xe3\x80\x81X-Real-IP和X-Forwarded-For.
\ne.g.

\n
server {\n  # the port your site will be served on\n    listen 80;\n  # the domain name it will serve for\n    server_name <your_host_name>; # substitute your machine's IP address or FQDN\n\n#    add_header Access-Control-Allow-Origin *;\n    # add_header Access-Control-Allow-Credentials: true;\n    add_header Access-Control-Allow-Headers Content-Type,XFILENAME,XFILECATEGORY,XFILESIZE;\n    add_header access-control-allow-headers authorization;\n    # Finally, send all non-media requests to the Django server.\n    location / {\n        proxy_pass http://127.0.0.1:8000/; # the uvicorn server address\n        proxy_set_header   Host             $host;\n        proxy_set_header   X-Real-IP        $remote_addr;\n        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;\n    }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

在 nginx 文档上:

\n
This middleware can be applied to add HTTP proxy support to an\napplication that was not designed with HTTP proxies in mind. It\nsets REMOTE_ADDR, HTTP_HOST from X-Forwarded headers. While\nWerkzeug-based applications already can use\n:py:func:werkzeug.wsgi.get_host to retrieve the current host even if\nbehind proxy setups, this middleware can be used for applications which\naccess the WSGI environment directly\xe3\x80\x82\nIf you have more than one proxy server in front of your app, set\nnum_proxies accordingly.\nDo not use this middleware in non-proxy setups for security reasons.\nThe original values of REMOTE_ADDR and HTTP_HOST are stored in\nthe WSGI environment as werkzeug.proxy_fix.orig_remote_addr and\nwerkzeug.proxy_fix.orig_http_host\n:param app: the WSGI application\n:param num_proxies: the number of proxy servers in front of the app.  \n
Run Code Online (Sandbox Code Playgroud)\n


Ali*_*AzG 5

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