SuperAPI AI 工具集

drupal-simple-oauth

安全与审计

查看项目

更新于 10-15 17:57

技能介绍

用途

本技能提供在 Drupal 中使用 simple_oauth 模块实现 OAuth2 认证的完整模式,适用于 API 认证、移动应用令牌以及 OAuth2 实现相关工作。文档基于 simple_oauth 6.1.1(consumers 8.x-1.24)验证,scope provider 为 dynamic(基于角色的粒度),兼容 Drupal 10.x/11.x。

核心机制

TokenAuthUser:核心认证包装器

当请求通过 OAuth 令牌认证时,Drupal 将用户包装进 TokenAuthUser 装饰器,同时强制校验令牌 AND 用户两者的权限。

位置:<webroot>/modules/contrib/simple_oauth/src/Authentication/TokenAuthUser.php(<webroot> 依项目为 web/ 或 docroot/)。

权限检查逻辑(第 95 行):用户 #1 拥有所有权限;否则返回 $this->token->hasPermission($permission) && $this->subject->hasPermission($permission)。关键规则:令牌和用户必须同时拥有该权限(AND 条件)。

角色交集逻辑(第 107 行):仅同时存在于令牌和用户中的角色被授予(array_intersect)。

Scope/角色匹配要求

OAuth 令牌要授予某权限,必须同时满足:用户拥有带该权限的角色;令牌请求包含与该角色匹配的 scope;存在同名 OAuth2 scope 实体。任一条件失败则权限被拒绝。

常见陷阱

陷阱 1:Scope/角色不匹配。用户有 administrator 角色,但令牌以 scope=api_consumer 请求,则只授予 authenticated 角色,administrator 权限被拒绝。解决:以正确的 scope(如 administrator)请求令牌。

陷阱 2:不存在的 Scope 实体。移动应用请求 scope=subscriber 但没有对应 OAuth2 scope 实体时,令牌没有 scopes、roles 和权限。解决:创建 scope 实体或使用现有 scope 名称。可通过 drush config:get simple_oauth.settings 或 SQL 查询现有 scope。

陷阱 3:假设 authenticated 角色权限总是被授予。实际上仅当令牌 scope 交集包含 authenticated 角色时才成立。该问题在 issue #3451692 中修复(含于 6.0.x)。

与其他模块集成

field_permissions:其 hasFieldAccess 调用 $account->hasPermission();若 $account 是 TokenAuthUser,则使用 AND 逻辑。当 scope 与角色匹配时,field_permissions 与 simple_oauth 正常工作。

JSON:API:遵循所有字段级访问检查,包括 field_permissions。JSON:API 调用 entity_field_access 钩子,field_permissions 检查 hasPermission;若账号是 TokenAuthUser,令牌与用户都必须有权限,任一失败则字段从 JSON:API 响应中排除。scope 正确时无需特殊配置。

OAuth 客户端配置

移动/解耦应用作为 OAuth 客户端的令牌请求模式:使用 FormData 追加 client_id、client_secret、scope(必须匹配用户角色)、grant_type=password、username、password;client_id 与 client_secret 应安全存储(如环境变量或安全配置上下文)。

创建 OAuth 客户端:simple_oauth 不提供创建客户端的 Drush 命令(仅 simple-oauth:generate-keys);客户端是 consumer 内容实体,可通过 UI(/admin/config/services/consumer/add)或实体 API 创建。

Consumer TTL 与社交登录受众:consumer 令牌 TTL 是内容实体数据而非配置,cim 不会部署它们,需在 hook_update_N() 中按 client_id 加载 consumer 进行修改;社交/Google 令牌授权必须对照允许列表检查 aud,移动应用以各自 client id 签发令牌,单一预期 aud 会导致所有应用登录失败。

Scope 实体管理

创建 scope 实体:通过配置 YAML(如 config/install/simple_oauth.oauth2_scope.subscriber.yml),包含 langcode、status、id、name、description、grant_types(refresh_token、password)、umbrella、parent、granularity_id(role)、granularity_configuration(role: subscriber)。

动态 Scope Provider 配置:simple_oauth.settings.yml 中设置 scope_provider: 'dynamic',scope 直接映射到角色。

列出 scope:可通过 SQL 查询 config 表中名称以 simple_oauth.oauth2_scope.% 的配置,或使用 drush config:get simple_oauth.oauth2_scope.subscriber。

最佳实践

  1. 将 scope 匹配到用户角色:先检查用户角色再请求匹配的 scope,避免硬编码可能不匹配的 scope。
  2. 为所有 API 角色创建 scope 实体。
  3. 需要时请求多个 scope(空格分隔),令牌将包含与用户角色匹配的所有 scope。
  4. 使用具体权限而非宽泛权限,例如字段级权限 view field_premium_content。
  5. 使用非管理员用户测试:管理员(uid=1)会绕过所有权限检查,应始终用普通用户测试 OAuth。

故障排查清单

OAuth 权限失败时检查:OAuth2 scope 实体是否存在;用户是否具有所需角色;角色是否拥有所需权限;令牌请求是否包含正确的 scope;scope 请求是否匹配用户角色(最常见问题);是否用 uid=1 测试(不要);simple_oauth scope provider 是否设为 dynamic。

参考资料

references/debugging-tokens.md:令牌权限被拒且排查清单无法解释时使用,含 scope 实体、用户角色和角色权限的逐步检查、铸造测试令牌并打印令牌/用户/交集角色的 PHP 脚本,以及真实 API 请求的 curl 脚本。

references/bearer-csrf-bypass.md:携带有效 Bearer 令牌的请求因客户端同时发送会话 cookie(React Native、webviews)而返回 403 CSRF 错误时使用,涉及 session_configuration 装饰器及其服务定义和测试方法。

references/consumer-ttls-and-social-grant.md:修改 consumer 访问/刷新令牌 TTL(内容实体,cim 不会部署),或编写必须对照允许列表检查 aud 的社交/Google 令牌授权时使用。

相关文档:Drupal.org Issue #3451692「Dynamic scope with role granularity does not inherit authenticated permissions」(6.0.x 修复)。

常用命令

列出 OAuth 客户端、列出 OAuth scopes、列出每个客户端的默认 scopes、检查用户角色、为用户添加角色、检查角色权限、查看 OAuth 设置、测试令牌端点(curl POST 至 /oauth/token,携带 grant_type=password、client_id、client_secret、username、password、scope)等命令均以代码形式列出。

关键文件参考

TokenAuthUser.php(含 AND 权限逻辑的核心认证包装器)、Role.php(动态 scope 到角色映射,第 95 行 authenticated 角色授予)、field_permissions.module(字段访问钩子,第 34 行)、CustomAccess.php(字段权限类型,第 36 行 hasPermission 调用)、src/Session/BearerSessionConfiguration.php(Bearer 令牌的 CSRF 绕过装饰器,自定义模块)。