背景

前段时间在折腾小程序,给小程序加了一些新的功能。

(不得不说,cursor是真的好用,给我改的页面是真的很符合我的审美~)

因为加了一些功能,其中有部分接口我想给限制一下访问ratelimit,比如“限制单个用户IP每分钟访问特定接口10次”。这对于防止滥用、保障服务稳定性至关重要。

环境介绍

因为我用的是fastapi做的后端服务,所以我的需求就是找一个fastapi可以用的ratelimit包。

我作为资深调包侠,在万能的github上找到了一个包 https://github.com/laurentS/slowapi

用起来很简单,一气呵成。

from slowapi import Limiter
from slowapi.util import get_ipaddr


limiter = Limiter(key_func=get_ipaddr, headers_enabled=True)


@router.get("/test/", response_model=BaseResponse)
@limiter.limit("10/minute")
async def test(request: Request, response: Response, poetry_id: str):
    """
        测试
    :return:
    """
    return {"test": 123}

slowapi.util 提供了两个主要的方法来获取请求的 IP 地址:

  1. get_ipaddr(request: Request) -> str:

    • 依赖代理头(如 X-Forwarded-For)。

    • 当你的应用部署在反向代理(如 Nginx)之后时,这是获取真实用户 IP 的推荐方式。

    • 注意: 需要正确配置反向代理以传递正确的头部信息,否则可能获取到代理服务器的 IP 或伪造的 IP。

  2. get_remote_address(request: Request) -> str:

    • 不直接依赖代理头,而是获取直接连接到应用服务器的客户端 IP。

    • 在 Gunicorn + Uvicorn 正确配置(例如使用了 ProxyHeadersMiddleware 或 Gunicorn 的 forwarded_allow_ips)的情况下,它也可以返回真实用户 IP。

    • 如果应用直接暴露给公网,或者代理服务器配置不当,此方法可能返回的是代理服务器的 IP。

from starlette.requests import Request


def get_ipaddr(request: Request) -> str:
    """
    Returns the ip address for the current request (or 127.0.0.1 if none found)
     based on the X-Forwarded-For headers.
     Note that a more robust method for determining IP address of the client is
     provided by uvicorn's ProxyHeadersMiddleware.
    """
    if "X_FORWARDED_FOR" in request.headers:
        return request.headers["X_FORWARDED_FOR"]
    else:
        if not request.client or not request.client.host:
            return "127.0.0.1"

        return request.client.host


def get_remote_address(request: Request) -> str:
    """
    Returns the ip address for the current request (or 127.0.0.1 if none found)
    """
    if not request.client or not request.client.host:
        return "127.0.0.1"

    return request.client.host

这里我直接用了get_ipaddr,下面是一个对比表格:

函数名

是否依赖代理头

是否推荐使用

备注

get_ipaddr()

✅ 是

✅ 推荐

适合自定义方式处理头部,灵活但要注意安全

get_remote_address()

❌ 否

✅ 推荐(在 Gunicorn+Uvicorn 正确配置下)

简洁、安全,靠 Gunicorn 自动处理 headers

我选择了 get_ipaddr,因为我的部署架构中包含了 Nginx 反向代理。

因为我启用了 headers_enabled=True ,HTTP 响应头中会包含 X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset 等信息,非常直观。

部署

在服务器上部署最新的代码之后,不出意外肯定会出意外~

这里说说我的后端服务的架构。很简单,很常规的用了nginx搞了一个反向代理。

部署新代码后,问题出现了:FastAPI 通过 get_ipaddr 获取到的 IP 地址总是 Nginx Proxy Manager 所在 Docker 容器的内部 IP,而不是真实用户的 IP! 这导致限流策略对所有用户都使用了同一个 IP,失去了应有的效果。

这个问题在使用了反向代理的架构中非常常见。原因是,对于 FastAPI 应用来说,直接连接它的是 Nginx,所以 request.client.host 默认是 Nginx 的 IP。而 X-Forwarded-For 头部虽然被 Nginx 设置了,但 Gunicorn/Uvicorn 需要被告知信任这个头部。

这里需要修改一下gunicorn的启动配置,增加forwarded_allow_ips配置,这里本来最好是设置一个指定的本地ip,不过因为我这个fastapi服务没有开公网端口,所以我这边是直接用的*。

forwarded_allow_ips = "*"
worker_class = "uvicorn.workers.UvicornWorker"

gunicorn forwarded_allow_ips 配置, gunicorn uvicorn X-Forwarded-For, fastapi gunicorn 获取真实ip

正常来说加上这个配置之后,fastapi服务还是不可以获取到用户的真实IP。

我们还需要在nginx的配置文件增加下面的配置:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;

(不过我用的是nginx proxy manager,里面是自带了这些配置~)

nginx proxy manager默认的配置:

  location / {
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_http_version 1.1;
    
    # Proxy!
    include conf.d/include/proxy.conf;
  }

查看conf.d/include/proxy.conf

关键点: 即使 Nginx 设置了 X-Forwarded-For,如果 Gunicorn/Uvicorn (FastAPI 的运行环境) 不信任这个头部(即没有配置 forwarded_allow_ips 或类似机制),get_ipaddr (或其他依赖此头部的方法) 仍然可能无法获取到真实的用户 IP。两端都需要正确配置。

nginx proxy manager X-Forwarded-For 设置, nginx proxy_set_header 获取真实ip, fastapi nginx proxy manager ip 地址问题

总结和最佳实践

为 FastAPI 应用(尤其是在小程序后端这样的场景)实现基于用户 IP 的接口限流,需要:

  1. 选择合适的限流库: slowapi 是一个优秀的选择。

  2. 正确获取用户 IP: 使用 get_ipaddr 并确保反向代理和应用服务器配置正确。

  3. 配置 Gunicorn: 设置 forwarded_allow_ips 以信任来自 Nginx 的 X-Forwarded-For 头部。为了安全,应指定具体的代理服务器 IP 而不是 *

  4. 配置 Nginx/Nginx Proxy Manager: 确保 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Real-IP $remote_addr; 被正确设置。

通过以上步骤,你的 FastAPI 应用就能准确地获取到真实用户 IP,并有效地实施接口限流策略了。这不仅提升了应用的安全性,也保证了服务的公平性和稳定性。

FastAPI接口限流教程, 小程序后端API防刷, 解决FastAPI获取不到真实IP, Python FastAPI生产部署IP问题, SlowAPI与Nginx集成指南, UvicornWorker真实IP获取方法