通用面试:如何安全使用 URI Template 生成 API 链接?
题干与适用场景
团队希望用 URI Template 统一生成 API 文档、分页链接和批量查询 URL,例如 /users{?status,limit}。模板来自配置文件,变量值部分来自用户输入。请说明 RFC 6570 的表达式级别、保留字符与列表展开规则,并设计安全的解析、扩展和验证边界。
面试官考察点
- 是否能区分模板语法、变量值编码与最终 URI 的解析验证。
- 能否解释简单展开、保留字符展开、路径/查询/矩阵参数展开的差异。
- 是否处理列表、关联数组、explode、前缀截断和未定义变量。
- 能否识别不可信模板带来的 SSRF、路径穿越、开放重定向和敏感信息泄露风险。
回答前需要澄清的问题
- 模板是静态代码、受审核配置,还是租户或用户可以提交?
- 生成结果只用于展示,还是会被服务端 HTTP 客户端直接请求?
- 变量是字符串、列表、关联数组,还是允许嵌套 JSON?
- 允许哪些 scheme、host、端口和路径前缀,是否必须限制在当前 API origin?
- 需要兼容 RFC 6570 的全部级别,还是只支持审核过的操作符子集?
30 秒回答框架
我会把模板解析、变量编码和最终 URI 策略分开。先定义支持的 RFC 6570 操作符与数据类型,再按操作符执行百分号编码、列表和关联数组展开;未定义变量应按规范省略。扩展后把结果当作不可信 URI,解析并校验 scheme、host、端口和规范化路径,禁止模板决定服务端可访问的网络目标。测试覆盖保留字符、Unicode、空值、重复键、前缀和恶意路径。
分步骤深入解答
第一步:明确 URI Template 的边界
URI Template 是用变量表达 URI-reference 的模板语法,不是 HTTP 客户端、URL 白名单或业务路由器。实现必须先解析字面文本和表达式,再根据变量值生成结果;不能把模板字符串直接拼接到请求中,也不能把扩展结果当成已经安全的 URL。
第二步:按级别实现操作符
RFC 6570 从简单变量展开开始,逐步覆盖保留字符、路径片段、标签、路径段、矩阵参数以及查询和查询延续。常见操作符包括 +、#、.、/、;、? 和 &。操作符决定分隔符、空值表现和哪些保留字符可以保留,不能用一个通用字符串替换规则代替。
第三步:处理值编码与复合值
简单字符串中的保留字符应按表达式规则百分号编码。列表可用逗号连接或通过 explode 变成重复参数;关联数组也有不同的键值分隔方式。前缀修饰符只取字符串前缀长度,不能把它误当作 Unicode 字符数、字节数或安全截断。实现应明确 UTF-8、空字符串和未定义变量的行为。
第四步:隔离模板来源与变量来源
静态、代码评审过的模板可以支持更完整的操作符;租户配置应限制表达式、变量名和输出组件。变量值可以来自请求,但模板不应允许调用函数、读取环境变量或拼接任意 scheme。把模板 AST 与变量字典分离,避免通过变量名注入新的表达式语法。
第五步:校验最终 URI
扩展完成后用标准 URI 解析器读取 scheme、authority、path、query 和 fragment。若结果用于服务端请求,scheme、host、端口必须匹配 allowlist,解析 DNS 后还要阻断回环、链路本地和私网地址,并在重定向时重新校验。路径规范化后再检查根路径,避免编码后的 .. 绕过规则。
第六步:设计 API 链接的可维护性
分页链接模板应明确哪些变量由服务端生成,过滤条件应采用固定变量名和类型。不要把签名、访问令牌或内部主机名放入可公开的模板或生成结果。记录模板版本、变量 schema 和扩展错误,结果需要稳定排序时由业务层保证,而不是依赖参数字符串顺序。
第七步:建立对照测试与监控
测试每个操作符的单变量、列表、关联数组、空值和未定义值,比较规范示例的期望结果。加入保留字符、Unicode、重复查询键、过长前缀、双重编码、%2e%2e、不同 scheme、重定向和 DNS 解析的安全用例。监控扩展失败率、拒绝原因、目标 origin 分布和异常长度,发现模板配置变化时触发审核。
高质量示范回答
我会先限制支持的 RFC 6570 操作符,并把模板 AST、变量 schema、百分号编码和最终 URI 校验拆开。列表、关联数组、explode 和前缀修饰符按各自规则处理,未定义变量省略;不允许变量值重新解释为表达式。扩展结果始终视为不可信 URI,解析后只允许当前 API 的 scheme、host、端口和规范化路径,服务端请求还要阻断私网地址并在每次重定向时复检。测试覆盖 RFC 示例、Unicode、空值、重复键、双重编码、路径穿越和 SSRF,记录模板版本与拒绝原因。
常见错误
- 用字符串拼接代替操作符语义,导致查询分隔符或百分号编码错误。
- 把
+保留字符展开当成“完全不编码”,忽略表达式规则和 URI 组件边界。 - 把列表、关联数组和 explode 当成同一种逗号连接格式。
- 只校验模板文本,不校验扩展后的 scheme、host、端口和规范化路径。
- 把 URL 编码当作 SSRF 防护,允许服务端跟随未经复检的重定向。
追问及应对
追问一:未定义变量应该怎样处理?
按表达式规则省略未定义变量及其必要分隔符,不应把它渲染成字符串 null。业务层若要求必填,应在扩展前依据变量 schema 报错。
追问二:为什么不能只调用 URL encode?
编码方式由表达式和 URI 组件决定,查询参数、路径段和保留字符展开的分隔符不同。单一编码函数无法决定复合值、空值、前缀和操作符行为。
追问三:模板来自租户时如何降低风险?
使用审核后的操作符子集、固定变量 schema 和输出组件,不允许任意 scheme 或 authority。扩展后仍执行 origin allowlist、路径规范化、DNS/IP 检查和重定向复检。
追问四:前缀修饰符能用于截断敏感字符串吗?
它是 URI Template 的字符串前缀语义,不是隐私脱敏或 Unicode 安全截断。敏感数据应在业务层显式掩码,长度和字符边界也要由业务规则定义。
追问五:如何证明实现兼容 RFC 6570?
运行 RFC 6570 的规范示例和各级操作符测试,比较每个变量类型的期望展开结果;再补充项目自己的安全拒绝用例,并记录未支持的级别和差异。