部署
在反向代理后面运行 admin 会带来两件在本地开发中通常可以忽略的事情:密钥必须在各个工作进程之间保持一致;生成的 URL 必须体现 HTTPS,即使应用从代理那里看到的始终只是纯 HTTP。
import os
from sqlalchemy import create_engine
from starlette.applications import Starlette
from starlette_admin.contrib.sqla import Admin, ModelView
from myapp.models import Post
engine = create_engine(os.environ["DATABASE_URL"])
app = Starlette()
admin = Admin(
engine,
title="My Admin",
base_url="/admin",
secret_key=os.environ["ADMIN_SECRET_KEY"],
)
admin.add_view(ModelView(Post))
admin.mount_to(app)
密钥
如果不传入 secret_key,Admin 会在启动时生成一个随机值。这对单个本地进程没有问题,但每个工作进程都会独立生成自己的密钥,因此由某个工作进程签署的 CSRF 令牌无法在其他工作进程上通过验证。在运行多个进程之前,请从环境变量设置 secret_key。完整的多工作进程故障模式以及密钥的使用方式,请参见安全。
反向代理与 HTTPS
Admin 通过调用 request.url_for(...) 来构建每一个内部链接(列表页、编辑表单、导出、/static 挂载,以及通过 /_files/... 提供的上传文件),其协议取自传入的请求。当 Nginx、Caddy 或 Traefik 等代理终结 TLS 并将纯 HTTP 转发给应用时,Starlette 无法得知原始请求是 HTTPS,除非代理发送 X-Forwarded-Proto 并且你的 ASGI 服务器信任它。如果不做此配置,生成的链接会降级为 http://,而当页面本身是通过 HTTPS 加载时,浏览器会阻止或重写这些链接。
需要在两处修复:
-
代理转发该请求头:
-
Uvicorn 信任它:通过
--proxy-headers加上指定代理 IP 的--forwarded-allow-ips(如果代理只能从内部网络访问,则使用'*'):
Gunicorn 的 uvicorn.workers.UvicornWorker 同样从 --forwarded-allow-ips 读取这两项设置。完整的进程管理器选项请参见 Uvicorn 部署文档。
Warning
--forwarded-allow-ips='*' 会信任来自任意来源的转发请求头。只有当应用除了通过代理之外无法被访问时才可使用它,例如应用绑定到私有网络或 Unix 套接字时。如果应用可以被直接访问,请将该设置限制为代理的实际 IP。否则,客户端可以直接伪造 X-Forwarded-Proto 和 X-Forwarded-For。
静态资源
admin 的 CSS 和 JS 随 starlette_admin 包一起发布,由 Admin 自己通过 base_url 下的 /static 挂载提供服务,而不是来自独立的静态主机。static_dir 仅允许覆盖个别文件(参见模板),并不能把资源服务从你的应用进程中移走。没有内置选项可以从 CDN 提供这些资源。如有需要,请改为在代理层为 {base_url}/static/* 添加一条对缓存友好的 Cache-Control 规则。
上传文件的情况有所不同:LocalStorage 同样通过应用提供文件(路径为 /_files/{storage}/{path},因此认证中间件仍然生效),但 S3Storage 及其他远程后端可以直接由服务提供商提供文件。参见文件存储。