繁体   English   中英

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

[英]FastAPI (starlette) get client real IP

我在 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'}

我做错了什么? 如何获得真正的 IP(如 Flask request.remote_addr)?

request.client应该可以工作,除非您在代理(例如 nginx)后面运行,在这种情况下使用 uvicorn 的--proxy-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.  

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}

有了这个例子,我就可以节省 10 分钟的时间来研究Starlette 的 Request 类

您不需要设置--proxy-headers bc 它默认启用,但它只信任来自--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]'在 localhost 上同时捕获 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

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

您将使用以下代码从客户端获取真实 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'}

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

在独立的基于 ubuntu 的 Web 服务器实例/Droplet(Amazon EC2 / DigitalOcean / Hetzner / SSDnodes)上分享对我有用的Apache服务器设置。 TL;DR:使用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 参数 - 如果您的应用程序不是部署在 URL 的根级别,这很重要。
这会处理 url 映射,因此在上面的程序代码中,我们不需要将其包含在@app.get行中。 使程序可移植 - 明天您可以将其从 /site1 移动到 /site2 路径,而无需编辑代码。

在服务器设置中

我的网络服务器上的设置:

  • Apache 服务器已设置
  • LetsEncrypt SSL 已启用
  • 编辑 /etc/apache2/sites-available/[sitename]-le-ssl.conf
  • 在 <VirtualHost *:443> 标签内添加这些行:
    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 端点,并且应该在响应中看到与您在https://www.whatismyip.com/上看到的 IP 地址相同的 IP 地址.

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

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

- FORWARDED_ALLOW_IPS=*

  1. 按照@allenren 的建议将代码添加到 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;
}

使用 Header 依赖项应该可以让您访问 X-Real-IP header。

from fastapi import FastAPI, Depends, Header

app = FastAPI()

@app.get('/')
def index(real_ip: str = Header(None, alias='X-Real-IP')):
return real_ip

现在,如果您启动服务器(在本例中为端口 8000)并使用该 X-Real-IP header 设置的请求来访问它,您应该会看到它回显。

http:8000/ X-Real-IP:111.222.333.444

HTTP/1.1 200 OK
content-length: 17 
content-type: application/json
server: uvicorn

“111.222.333.444”

如果您使用 nginx 作为反向代理; 直接的解决方案是像这样包含proxy_params file

location /api {
    include proxy_params;
    proxy_pass http://localhost:8000;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM