8. 安全考量 (Security Considerations)
8.1. 保护授权码 (Protecting the Authorization Code)
第 7 节记录的重定向 URI 选项都有一个共同优点: 只有同一设备上的原生应用或该应用自己的网站能够接收授权码, 从而限制攻击面. 但是, 运行在同一设备上的其他原生应用可能会拦截该代码.
使用私用 URI scheme 作为重定向 URI 的一个限制是, 多个应用通常可以注册同一个 scheme, 这会使哪个应用接收授权码变得不确定. PKCE [RFC7636] 第 1 节详细说明了如何利用此限制执行代码拦截攻击.
在某些操作系统上, 基于环回 IP 的重定向 URI 可能会被访问同一环回接口的其他应用拦截.
由于存在 URI authority, 应用声明的 "https" scheme 重定向较不易受到 URI 拦截, 但该应用仍然是公共客户端. 此外, URI 是通过操作系统的 URI 分发处理程序发送的, 其安全属性未知.
PKCE [RFC7636] 协议专门为缓解这种攻击而创建. 它是 OAuth 2.0 的持有证明 (proof-of-possession) 扩展, 可在授权码被拦截时防止其被使用. 为提供保护, 该扩展让客户端生成一个秘密 verifier. 客户端在初始授权请求中传递该 verifier 的哈希值, 并且在兑换授权码时 MUST 提供未哈希的 verifier. 拦截授权码的应用并不拥有该秘密, 因而该代码无法发挥作用.
第 6 节要求客户端和服务器都对公共原生应用客户端使用 PKCE. 授权服务器 SHOULD 通过返回错误消息来拒绝来自未使用 PKCE 的原生应用的授权请求, 如 PKCE [RFC7636] 第 4.4.1 节所定义.
8.2. OAuth 隐式授权许可流程 (OAuth Implicit Grant Authorization Flow)
OAuth 2.0 隐式授权许可流程 (在 OAuth 2.0 [RFC6749] 第 4.2 节中定义) 通常可以配合这种实践使用: 在浏览器中执行授权请求, 并通过基于 URI 的应用间通信接收授权响应. 但是, 由于隐式流程无法由 PKCE [RFC7636] 保护 (第 8.1 节要求使用 PKCE), 原生应用 NOT RECOMMENDED 使用隐式流程.
通过隐式流程授予的访问令牌也无法在没有用户交互的情况下刷新, 因此对于需要刷新访问令牌的原生应用授权, 能够签发刷新令牌的授权码许可流程是更实用的选项.
8.3. 环回重定向考量 (Loopback Redirect Considerations)
环回接口重定向 URI 使用 "http" scheme (即不使用传输层安全 (Transport Layer Security, TLS)). 对于环回接口重定向 URI, 这是可以接受的, 因为 HTTP 请求永远不会离开该设备.
客户端只应在启动授权请求时打开网络端口, 并在响应返回后立即关闭该端口.
客户端只应监听环回网络接口, 以避免受到其他网络参与方干扰.
虽然使用 localhost 的重定向 URI (即 http://localhost:{port}/{path}) 在功能上类似于第 7.3 节所述的环回 IP 重定向, 但 NOT RECOMMENDED 使用 localhost. 使用环回 IP 字面量而不是 localhost 指定重定向 URI, 可以避免意外监听环回接口以外的网络接口. 它也较不容易受到客户端防火墙以及用户设备上主机名解析配置错误的影响.
8.4. 原生应用客户端注册 (Registration of Native App Clients)
除非使用动态客户端注册 (Dynamic Client Registration) [RFC7591] 等机制为每个实例配置 secret, 原生应用会按 OAuth 2.0 [RFC6749] 第 2.1 节的定义归类为公共客户端. 它们 MUST 以公共客户端身份向授权服务器注册. 授权服务器 MUST 在客户端注册详情中记录客户端类型, 以便相应地识别并处理请求.
授权服务器 MUST 要求客户端注册其完整重定向 URI (包括 path 组件), 并拒绝指定重定向 URI 与已注册值不完全匹配的授权请求. 环回重定向是例外, 其除 port URI 组件外仍要求精确匹配.
对于基于私用 URI scheme 的重定向, 授权服务器 SHOULD 执行第 7.1 节的要求, 即客户端使用基于反向域名的 scheme. 至少, 任何不包含句点字符 (".") 的私用 URI scheme 都 SHOULD 被拒绝.
除具备抗碰撞属性外, 要求 URI scheme 基于应用控制下的域名, 也有助于在两个应用声明同一私用 URI scheme (其中一个应用恶意行事) 而发生争议时证明所有权. 例如, 如果两个应用都声明 "com.example.app", "example.com" 的所有者可以请求应用商店运营者移除仿冒应用. 如果使用通用 URI scheme, 此类请求会更难证明.
授权服务器 MAY 要求包含其他平台特定信息, 例如应用包名或 bundle 名称, 或者在支持此类功能的操作系统上可能有助于验证调用应用身份的其他信息.
8.5. 客户端认证 (Client Authentication)
作为分发给多个用户的应用的一部分而静态包含的 secret 不应被视为机密 secret, 因为某个用户可以检查自己的副本并获知该共享 secret. 基于这个原因以及 [RFC6819] 第 5.3.1 节所述原因, 授权服务器 NOT RECOMMENDED 要求公共原生应用客户端使用共享 secret 进行客户端认证, 因为这除了提供客户端标识外几乎没有价值, 而客户端标识已经由 "client_id" 请求参数提供.
仍要求原生应用客户端静态包含共享 secret 的授权服务器 MUST 将该客户端视为公共客户端 (如 OAuth 2.0 [RFC6749] 第 2.1 节所定义), 并且不得将该 secret 接受为客户端身份的证明. 如果没有额外措施, 此类客户端会受到客户端冒充攻击 (见第 8.6 节).
8.6. 客户端冒充 (Client Impersonation)
如 OAuth 2.0 [RFC6749] 第 10.2 节所述, 除非能够确保客户端身份, 否则授权服务器 SHOULD NOT 在没有用户同意或交互的情况下自动处理授权请求. 这包括用户此前已经批准某个给定 client id 的授权请求的情况: 除非能够证明客户端身份, 否则该请求 SHOULD 像此前没有任何请求被批准一样处理.
授权服务器 MAY 接受声明式 "https" scheme 重定向等措施作为身份证明. 某些操作系统可能提供替代的平台特定身份功能, 可酌情接受这些功能.
8.7. 伪造的外部用户代理 (Fake External User-Agents)
发起授权请求的原生应用对用户界面有很大程度的控制权, 因此可能呈现一个伪造的外部用户代理, 也就是让嵌入式用户代理看起来像外部用户代理.
当所有良性参与方都使用外部用户代理时, 其优势在于安全专家能够检测恶意参与方, 因为任何伪造外部用户代理的参与方都可以被证明是恶意的. 另一方面, 如果良性和恶意参与方都使用嵌入式用户代理, 恶意参与方就不需要伪造任何东西, 因而更难被检测. 一旦检测到恶意应用, 就可能利用这一认知在恶意软件扫描软件中将该应用的签名列入黑名单, 采取移除措施 (对于通过应用商店分发的应用), 以及采取其他步骤来降低恶意应用的影响和传播.
授权服务器也可以通过要求只有真正外部用户代理才可用的认证因子, 直接防范伪造的外部用户代理.
在使用应用内浏览器标签页时特别关注安全性的用户, 还可以额外采取一步操作: 从应用内浏览器标签页在完整浏览器中打开该请求, 并在那里完成授权, 因为应用内浏览器标签页模式的大多数实现都提供此类功能.
8.8. 恶意外部用户代理 (Malicious External User-Agents)
如果恶意应用能够在操作系统中将自己配置为 "https" scheme URI 的默认处理程序, 它就能够拦截使用默认浏览器的授权请求, 并滥用这种信任位置实现恶意目的, 例如对用户进行钓鱼攻击.
这种攻击并不限于 OAuth. 以这种方式配置的恶意应用会在原生应用使用 OAuth 的场景之外, 对用户构成一般性且持续的风险. 许多操作系统通过要求显式用户操作才能更改 "http" 和 "https" scheme URI 的默认处理程序来缓解此问题.
8.9. 跨应用请求伪造保护 (Cross-App Request Forgery Protections)
[RFC6819] 第 5.3.5 节建议使用 "state" 参数关联客户端请求和响应, 以防止 CSRF (Cross-Site Request Forgery, 跨站请求伪造) 攻击.
为缓解通过应用间 URI 通信通道进行的 CSRF 风格攻击 (即所谓的 "cross-app request forgery", 跨应用请求伪造), 同样 RECOMMENDED 原生应用在授权请求的 "state" 参数中包含高熵安全随机数, 并拒绝任何没有 state 值与待处理传出授权请求匹配的传入授权响应.
8.10. 授权服务器混淆缓解 (Authorization Server Mix-Up Mitigation)
为防止被攻陷或恶意的授权服务器攻击同一应用使用的另一个授权服务器, REQUIRED 对该应用使用的每个授权服务器使用唯一重定向 URI (例如通过改变 path 组件), 并且如果接收授权响应的重定向 URI 与传出授权请求中的重定向 URI 不匹配, 则拒绝该授权响应.
原生应用 MUST 将授权请求中使用的重定向 URI 与授权会话数据一起存储 (即与 "state" 和其他相关数据一起存储), 并且 MUST 验证接收授权响应的 URI 与之完全匹配.
第 8.4 节的要求, 特别是授权服务器拒绝 URI 与已注册值不匹配的请求这一要求, 也是防止此类攻击所必需的.
8.11. 非浏览器外部用户代理 (Non-Browser External User-Agents)
本最佳实践推荐一种特定类型的外部用户代理: 用户的浏览器. 其他外部用户代理模式也可能适用于安全且可用的 OAuth. 本文档不对此类模式作出评论.
8.12. 嵌入式用户代理 (Embedded User-Agents)
OAuth 2.0 [RFC6749] 第 9 节记录了原生应用与授权端点交互的两种方法. 本最佳当前实践要求原生应用 MUST NOT 使用嵌入式用户代理执行授权请求, 并允许授权端点 MAY 采取措施检测并阻止嵌入式用户代理中的授权请求. 这些要求的安全考量详见本节.
嵌入式用户代理是授权原生应用的一种替代方法. 按定义, 对于授权服务器的第三方而言, 使用这些嵌入式用户代理是不安全的, 因为托管嵌入式用户代理的应用可以访问用户的完整认证凭据, 而不只是原本要授予该应用的 OAuth 授权许可.
在典型的基于 web-view 的嵌入式用户代理实现中, 宿主应用可以记录登录表单中输入的每一次按键以捕获用户名和密码, 自动提交表单以绕过用户同意, 并复制会话 cookie 且使用它们以用户身份执行已认证操作.
即使由与授权服务器属于同一方的受信任应用使用, 嵌入式用户代理也会因能够访问超出其需要的更强凭据而违反最小权限原则, 可能增加攻击面.
鼓励用户在缺少浏览器通常具备的地址栏和可见证书验证功能的嵌入式用户代理中输入凭据, 会使用户无法知道自己是否正在登录合法站点. 即使他们确实登录的是合法站点, 这也会训练他们接受在未先验证站点的情况下输入凭据.
除安全顾虑外, 嵌入式用户代理不会与其他应用或浏览器共享认证状态, 因而要求用户为每个授权请求登录, 这通常被认为是较差的用户体验.