2026/9/16 21:41:35

Litestar Allowed Hosts 中间件:基于 Host 头的可信主机校验与 WWW 重定向实战

Litestar Allowed Hosts 中间件:基于 Host 头的可信主机校验与 WWW 重定向实战 Litestar Allowed Hosts 中间件基于 Host 头的可信主机校验与 WWW 重定向实战【免费下载链接】litestarLight, flexible and extensible ASGI framework | Built to scale项目地址: https://gitcode.com/GitHub_Trending/li/litestar导读本篇文章围绕 Litestar 内置的AllowedHostsMiddleware中间件展开讲解如何通过配置AllowedHostsConfig或直接传入域名列表对每个请求的Host/X-Forwarded-Host头进行可信主机校验从而抵御 Host 头注入、DNS rebinding 等安全风险。读完本文你将掌握该中间件的启用方式、全部配置参数、通配符子域规则、www.自动重定向机制以及其底层的正则匹配实现原理与测试验证方法。一、为什么需要 Allowed Hosts 校验HTTP 请求中的Host头由客户端自行控制。若应用在缺少校验的情况下直接信任该头攻击者可以通过伪造Host头实施Host 头注入攻击诱导应用生成指向恶意域名的链接密码重置邮件、OAuth 回调等配合DNS rebinding攻击绕过基于域名隔离的同源策略利用反向代理转发逻辑将请求路由到非预期后端。Litestar 提供的解决方案是要求每个传入请求携带Host头并将其严格限制在一组可信域名allowed hosts之内。校验失败的请求将直接被中间件以400 Bad Request拒绝不再进入路由处理流程。该功能在 内置中间件使用文档 中被归类为与 CORS、CSRF 并列的常用安全机制其 API 参考即本篇所依据的 allowed_hosts 参考文档。二、快速上手两种启用方式在创建Litestar应用时可通过allowed_hosts参数启用该中间件支持两种传参形式见 应用入口参数定义方式一直接传入域名列表from litestar import Litestar app Litestar( route_handlers[...], allowed_hosts[*.example.com, www.wikipedia.org], )方式二传入AllowedHostsConfig配置实例from litestar import Litestar from litestar.config.allowed_hosts import AllowedHostsConfig app Litestar( route_handlers[...], allowed_hostsAllowedHostsConfig( allowed_hosts[*.example.com, www.wikipedia.org], www_redirectTrue, ), )两种形式最终等价在 应用配置类 中如果传入的是普通列表会被自动包装为AllowedHostsConfig(allowed_hosts[...])实例随后在 路由树构建阶段AllowedHostsMiddleware会被包装进对应 handler 的 ASGI 中间件链中从而对http与websocket两类 scope 生效。三、AllowedHostsConfig配置参数详解AllowedHostsConfig定义于 配置源码全部字段如下参数类型默认值说明allowed_hostslist[str][*]可信主机列表。使用*.前缀可匹配任意子域组合*表示放行所有主机excludestr \| list[str] \| NoneNone路径模式单个或列表匹配到的请求路径将跳过该中间件exclude_opt_keystr \| NoneNone路由标识opt key在特定路由上设置后可单独跳过主机校验scopesScopes \| NoneNone中间件处理的 ASGI scope 类型为None时同时处理http与websocketwww_redirectboolTrue是否将请求重定向到可信的www.域名其中exclude、exclude_opt_key、scopes三个参数在构造时会透传给父类AbstractMiddleware见 中间件基类实现按路径 / 按路由 / 按 scope 类型动态跳过中间件的能力from litestar import Litestar, get from litestar.config.allowed_hosts import AllowedHostsConfig # 排除健康检查路径 config AllowedHostsConfig( allowed_hosts[example.com], exclude[/health, /metrics], ) # 通过 opt key 单独豁免某个路由 get(/webhook, opt{skip_host_check: True}) def webhook() - None: ... app Litestar( route_handlers[webhook], allowed_hostsAllowedHostsConfig( allowed_hosts[example.com], exclude_opt_keyskip_host_check, ), )四、通配符规则与配置校验4.1 通配符使用规则*.example.com会同时匹配www.example.com、x.y.z.example.com等任意层级的子域仅允许在域名开头使用*.前缀通配不允许出现在域名中间或末尾直接使用*表示放行所有主机效果等同于关闭中间件——此时更建议直接不启用该功能。4.2 非法通配符会触发启动异常AllowedHostsConfig.__post_init__见 配置校验逻辑会在实例化时校验通配符位置任何非*但包含*且不以*.开头的域名都会抛出ImproperlyConfiguredException提示信息为domain wildcards can only appear in the beginning of the domain, e.g. *.example.com例如www.moishe.*.com这样的配置会在应用启动前直接被拒绝。对应的 单元测试 验证了该行为with pytest.raises(ImproperlyConfiguredException): AllowedHostsConfig(allowed_hosts[www.moishe.*.com])五、底层实现原理源码级解析AllowedHostsMiddleware的核心实现位于 中间件源码可分为初始化编译正则与请求期匹配拦截两个阶段。5.1 初始化阶段把域名列表编译为正则在__init__中中间件会将配置的域名列表编译成一个正则表达式见 L34-L49if any(host * for host in config.allowed_hosts): return # 存在 * 时直接放行不编译正则 allowed_hosts: set[str] { rf.*\.{re.escape(host.replace(*., ))}$ if host.startswith(*.) else re.escape(host) for host in config.allowed_hosts } self.allowed_hosts_regex re.compile(|.join(sorted(allowed_hosts)))关键细节以*.开头的域名会被编译为.*\.example\.com$从而匹配任意层级子域普通域名使用re.escape转义确保点号等元字符被精确匹配多个域名用|连接并按字典序排序保证正则稳定、可预测若配置中存在*allowed_hosts_regex保持为None请求期直接放行——这与*即关闭校验的语义一致。测试断言可佐证正则形态见 tests/unit/test_middleware/test_allowed_hosts_middleware.py#L39-L53对于allowed_hosts[*.example.com, moishe.zuchmir.com]编译出的 pattern 为.*\.example\.com$|moishe\.zuchmir\.com且fullmatch(x.y.z.example.com)通过、fullmatch(www.example.x.com)被拒绝。5.2 请求期Host 头提取与匹配拦截每次请求进入时__call__执行如下流程见 L62-L79若allowed_hosts_regex is None配置含*直接调用下游self.app不做任何校验从 scope 中读取请求头取出host字段并通过host.split(:)[0]剥离端口号兼容example.com:8000这类带端口的 Host 头使用fullmatch对主机名做整体精确匹配匹配成功则放行若未匹配且www_redirect开启则尝试匹配www.重定向规则见下文以上均不满足时直接构造400 Bad Request响应response ASGIResponse(bodyb{message:invalid host header}, status_codeHTTP_400_BAD_REQUEST)拒绝响应的内容固定为 JSON{message:invalid host header}状态码为400。该行为在 参数化测试 中得到验证伪造的moisheAzuchmir.com、非法子域x.moishe.zuchmir.com均返回400而x.example.com、x.y.example.com等合法主机返回200。5.3www.自动重定向机制www_redirect默认开启True。当可信主机列表中存在以www.开头的域名时中间件会额外编译一组重定向正则见 L46-L49将www.example.com去掉前缀得到example.com并编译为精确匹配规则。请求期流程为当 Host 头既未匹配allowed_hosts_regex、却命中了redirect_domains时例如可信域名是www.moishe.zuchmir.com而请求 Host 为moishe.zuchmir.com中间件会基于当前请求 scope 重建 URL在 netloc 前补上www.并通过ASGIRedirectResponse发起重定向url URL.from_scope(scope) redirect_url url.with_replacements(netlocfwww.{url.netloc}) redirect_response ASGIRedirectResponse(pathstr(redirect_url)) await redirect_response(scope, receive, send)测试用例 验证了两种行为默认开启时访问http://moishe.zuchmir.com/会得到200且最终 URL 变为http://www.moishe.zuchmir.com/已重定向设置www_redirectFalse后同一请求直接返回400 Bad Request。重定向仅发生在去掉www.后恰好等于某个可信域名的场景因此不会影响其他未匹配主机它们仍会收到400。六、中间件在中间件栈中的位置与运维观察从 路由树映射源码 可以推断AllowedHostsMiddleware与其他内置中间件一样在应用构建阶段被包装到 handler 的 ASGI 中间件链最外层因此在路由处理、DTO 解析、响应序列化之前即完成 Host 校验保证非法主机请求不会触及任何业务代码。运维层面Litestar CLI 的litestar info命令会在应用信息表中展示当前生效的可信主机列表见 CLI 工具实现便于排查为什么某些域名被拒绝的配置问题。七、常见问题与最佳实践为什么配置了*请求仍然被拦不会——*的存在会让正则保持None并直接放行语义上等同关闭。若需放行大部分域名但封禁个别域名Allowed Hosts 中间件并不适合请考虑在更外层使用自定义 ASGI 中间件或反向代理层处理。校验的是哪个头中间件从 scope 请求头中读取host键即标准Host头。部署在代理之后时需确保代理正确改写Host头为上游可信域名否则请求会被误拒详见 内置中间件文档 中 Allowed Hosts 一节。端口号如何处理Host 头中的端口会在比较前被剥离example.com:8080与example.com视为同一主机。WebSocket 连接是否受保护是。scopes默认为None时同时覆盖http与websocket两类 scope如需缩小范围可显式传入 scope 集合。生产建议在公网环境将allowed_hosts收敛为明确的正式域名列表而非*配合www_redirectTrue统一入口域名可有效降低 Host 头注入类攻击面。八、相关资源中间件实现litestar/middleware/allowed_hosts.py配置类定义litestar/config/allowed_hosts.py应用集成示例与说明docs/usage/middleware/builtin-middleware.rst单元测试正则形态、重定向、放行与拒绝行为tests/unit/test_middleware/test_allowed_hosts_middleware.py中间件基类exclude / exclude_opt_key / scopes 机制litestar/middleware/base.py中间件在应用构建阶段的注册litestar/_asgi/routing_trie/mapping.py【免费下载链接】litestarLight, flexible and extensible ASGI framework | Built to scale项目地址: https://gitcode.com/GitHub_Trending/li/litestar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考