mailcow 内置 OAuth2 服务的版本演进:bshaffer/oauth2-server-php 1.x 变更史与安全修复解析
2026/9/15 13:09:19 网站建设 项目流程

mailcow 内置 OAuth2 服务的版本演进:bshaffer/oauth2-server-php 1.x 变更史与安全修复解析

【免费下载链接】mailcow-dockerizedmailcow: dockerized - 🐮 + 🐋 = 💕项目地址: https://gitcode.com/GitHub_Trending/ma/mailcow-dockerized

导读

mailcow(dockerized)的 Web 前端内置了一套完整的 OAuth2 授权服务,用于支撑第三方客户端(如移动邮件客户端、外部应用)以安全的方式获取用户授权并访问邮箱资源。这套服务所依赖的核心 PHP 库是 bshaffer/oauth2-server-php,本文以该库随仓库携带的 CHANGELOG.md 为主线,完整梳理其 1.0 到 1.10 七个版本的演进历程、关键特性引入与安全修复,并结合 mailcow 仓库中 prerequisites.inc.php、oauth 端点、数据库表结构 等真实集成代码,帮助读者理解:这套 OAuth2 服务在 mailcow 中如何被装配、哪些配置项与授权类型真正生效,以及历次升级背后修复了哪些安全与兼容性问题。


一、定位:mailcow 里的 OAuth2 服务与 bshaffer 库

mailcow 的 OAuth2 能力并非自研,而是通过 vendor 目录引入的bshaffer/oauth2-server-php库实现。该库在 composer.json 中定义为"bshaffer/oauth2-server-php"、许可证为 MIT、要求php >= 5.3.9,并通过psr-0规则将OAuth2命名空间映射到src/目录。

在 mailcow 中,OAuth2 服务的装配集中在 prerequisites.inc.php:

class mailcowPdo extends OAuth2\Storage\Pdo { public function __construct($connection, $config = array()) { parent::__construct($connection, $config); $this->config['user_table'] = 'mailbox'; } public function checkUserCredentials($username, $password) { if (check_login($username, $password, array("role" => "user", "service" => "NONE")) == 'user') { return true; } return false; } public function getUserDetails($username) { return $this->getUser($username); } } $oauth2_scope_storage = new OAuth2\Storage\Memory(array('default_scope' => 'profile', 'supported_scopes' => array('profile'))); $oauth2_storage = new mailcowPdo(array('dsn' => $dsn, 'username' => $database_user, 'password' => $database_pass)); $oauth2_server = new OAuth2\Server($oauth2_storage, array( 'refresh_token_lifetime' => $REFRESH_TOKEN_LIFETIME, 'access_lifetime' => $ACCESS_TOKEN_LIFETIME, )); $oauth2_server->setScopeUtil(new OAuth2\Scope($oauth2_scope_storage)); $oauth2_server->addGrantType(new OAuth2\GrantType\AuthorizationCode($oauth2_storage)); $oauth2_server->addGrantType(new OAuth2\GrantType\UserCredentials($oauth2_storage)); $oauth2_server->addGrantType(new OAuth2\GrantType\RefreshToken($oauth2_storage, array( 'always_issue_new_refresh_token' => true )));

这段代码直接映射了 CHANGELOG 中多个版本的核心能力:OAuth2\Storage\Pdo(1.4 引入 DSN 字符串构造、Postgres 支持)、三种 Grant Type(授权码、用户名密码、刷新令牌)、Scope工具类(1.3 起getDefaultScope会接收client_id)以及always_issue_new_refresh_token配置项(1.7 引入,1.10 默认值仍为false,但 mailcow 显式置为true)。

对应的 HTTP 端点位于 data/web/oauth:

  • authorize.php:调用$oauth2_server->validateAuthorizeRequest()handleAuthorizeRequest(),未登录用户会被重定向到/?oauth
  • token.php:一行代码$oauth2_server->handleTokenRequest($request)->send()处理令牌签发;
  • profile.php:先verifyResourceRequest()校验访问令牌,再按profile作用域返回邮箱用户信息(usernameidemailfull_name等)。

客户端的管理逻辑(添加、编辑、吊销令牌、续签密钥)在 functions.oauth2.inc.php 中通过oauth2('add'|'edit'|'delete'|'details', 'client', ...)完成,对应数据库表oauth_clients的结构定义于 init_db.inc.php。


二、1.x 版本时间线总览

CHANGELOG 记录了 1.x 系列从 2013-08-12 的 1.0 到 2017-11-15 的 1.10.0 共 11 个版本(其中 1.3 条目重复出现两次,内容一致,应为记录合并遗留)。整体演进呈现三条主线:

  1. 安全加固:从 token 生成随机性(1.4)、hash_equals()防时序攻击(1.6)、JWT 漏洞修复(1.7)、OpenSSL 随机源优先于 Mcrypt(1.9),到 1.10 的random_bytes与 CORS 修复;
  2. JWT 访问令牌体系:1.1 引入 JSON 加密令牌(crypto token),1.6 更名为JwtAccessToken/use_jwt_access_tokens,1.8 补jti与生命周期配置,1.10 开放createPayload子类化定制;
  3. 存储层扩展:PDO(内置 SQL、Postgres、DSN/PDO options 构造)→ Redis → Cassandra(1.3)→ DynamoDB(1.5)→ Couchbase、Mongo(1.6/1.9)→ MongoDB(1.9/1.10)。
版本日期里程碑
1.02013-08-12基础授权/令牌/资源端点;redirect_status_code配置
1.12013-12-17JSON 加密令牌(crypto token)、refresh token 配置、JWT Bearerjti防重放
1.22014-01-03空令牌返回 401、JWT 解码容错、predis参数修复
1.32014-02-27Cassandra 存储、use_crypto_tokens配置、非过期 refresh token、getDefaultScopeclient_id
1.42014-06-12OpenID Connect 支持、PDO 支持 DSN/Postgres/自定义 options、令牌生成随机化
1.52014-08-27DynamoDB、refresh token 不再加密(BC)、scope对 refresh token 可选、prompt=consent/none
1.62015-01-16更名JwtAccessToken、Couchbase、JWT claims 对齐规范、hash_equals()签名校验、JTI 表定义
1.72015-04-23PDOFETCH_ASSOC、Bearer header 大小写不敏感、validateRedirectUri公开、code id_token、JWT 漏洞修复、unset_refresh_token_after_use
1.82015-09-18jti、JWT 生命周期配置、令牌吊销(RFC 7009)、FirebaseJWT 桥接
1.92017-01-06client_secret可空、密码哈希算法可覆写、Token 响应 Content-Type 修正、RFC6750 兼容、redirect_uri 编码字符修复、MongoDB
1.10.02017-11-15createPayload开放、UserInfoController 构造简化、random_bytes优先、PHP 7.2count()修复、CORS 修复、PHPUnit 4/Test 类迁移

三、分版本深度解读

3.1 v1.0(2013-08-12):OAuth2 核心骨架成型

1.0 奠定了库的基础架构,修复条目中已能看到后续版本的基因:

  • 授权流程:修正授权步骤后的错误跳转(#171)、redirect_uri在授权码交换时可允许为 NULL 的文档示例(#163)、非法redirect_uri处理(#163);
  • 参数传递:client_id传入getDefaultScope()(#187)、配置传给HttpBasic客户端断言器(#191);
  • 令牌语义:refresh_token 响应中必须包含refresh_token(#176)、user_id在授权码/刷新令牌场景不再强制(#168/#174);
  • 便捷性:ResourceController新增getToken()(#162);
  • 配置:为AuthorizeController增加redirect_status_code配置参数(#203);
  • 安全与健壮性:open_basedir兼容(#227/#227 相关条目)、用户对象默认安全加固(#133)。

这些基础能力在 mailcow 中直接体现为 authorize.php 的validateAuthorizeRequest/handleAuthorizeRequest两段式调用——先校验授权请求合法性,再由用户交互决定$is_authorized并携带$_SESSION['mailcow_cc_username']作为user_id完成授权。

3.2 v1.1(2013-12-17):加密令牌与刷新令牌配置

1.1 是 JWT 之路的起点:

  • JSON 加密令牌支持(#245):访问令牌可携带加密的 JSON 载荷,而非数据库中的随机串;
  • JWT Bearerjti(#268):为 JWT Bearer 令牌实现jti(JWT ID)声明以防止重放攻击;
  • refresh token 配置(#278):Server类新增刷新令牌相关配置;
  • CryptoToken 刷新令牌生命周期(#253):修复加密令牌模式下刷新令牌的生命周期计算;
  • 公钥逻辑重构(#246)、Bearer 令牌类型一致化(#247)、Scope 存储范式对齐(#215/#228)、oauth_clients移除无用列(#230);
  • 安全修复:#274 修复了"提供空client_idclient_secret即被授予 API 访问"的漏洞。

3.3 v1.2(2014-01-03):错误语义与容错修复

  • 空令牌从 200 改为 401(#285);
  • 未提供令牌时按规范不返回错误消息(#286);
  • $jwt->decode()收到非法参数时不再抛出 PHP 警告(#280);
  • predis参数数量错误修复(#279);
  • 密码授权类型下保护 JS Web 应用客户端密钥(#277)。

3.4 v1.3(2014-02-27):Cassandra 与开发者体验

  • 新增Cassandra 存储(#311),仓库中 Storage/Cassandra.php 使用oauth_clients:前缀的 key 设计;
  • 用户凭据授权类型的响应码修正(#298);
  • use_crypto_tokens配置(#318):在Server类上启用加密令牌,改善开发者体验;
  • getDefaultScope接收client_id(#320,BC 变更);
  • 非过期刷新令牌(#335);
  • PDO 的getClientKey修复(#333)、Redis 的expireAuthorizationCode修复(#336)。

3.5 v1.4(2014-06-12):OpenID Connect 与存储能力大扩充

1.4 是该库功能密度最高的版本之一:

  • OpenID Connect 支持(#351):引入OAuth2\OpenID命名空间(Controller\UserInfoControllerResponseType\IdToken等,见 OpenID 目录);
  • PDO 存储:支持DSN 字符串构造(#189)、Postgres(#367)、构造器传入 PDO options(#384),这些能力正是 mailcowmailcowPdoarray('dsn' => ..., 'username' => ..., 'password' => ...)方式构造的基础;
  • Bearer 令牌:PUT 请求正文也可携带令牌(#233)、新增requestHasToken()(#349);
  • 访问令牌生成改用mcrypt_create_ivopenssl_random_pseudo_bytes(#368),强化随机性;
  • 请求对象对 header 大小写不敏感(#376);
  • BC 变更GrantType接口新增getQueryStringIdentifier()(#358)。

3.6 v1.5(2014-08-27):DynamoDB 与规范对齐

  • 新增DynamoDB 存储(#399),Storage/DynamoDB.php 以oauth_clients为表名、client_id为主哈希键;
  • BC 变更:refresh token 不再加密(#397);
  • malformed/expired 令牌错误名规范化(#404);
  • OpenID Connect:多 scope claims 修复、支持prompt参数(consent/none)(#412);
  • XML 输出修复(#411)、scope对刷新令牌存储变为可选(#423);
  • PDO SQL 随库捆绑(#354),即 OAuth2 所需的建表 SQL 内置于库中。

3.7 v1.6(2015-01-16):JWT 规范化与安全强化

  • CryptoToken更名为JwtAccessToken(#437),use_crypto_tokens更名为use_jwt_access_tokens——仓库 ResponseType/JwtAccessToken.php 即此更名产物,其createPayload()构建id/jti/iss/aud/sub/exp/iat/token_type/scope载荷;
  • 新增Couchbase 存储(#447);
  • JWT claims 对齐规范(#460),即iss/aud/sub/exp等标准声明名;
  • 多值response_type顺序无关(#470),对应Server::normalizeResponseType()对含空格类型做字典序排序的实现;
  • validateAuthorizeRequest支持 POST 与 GET(#471);
  • 新增JTI 表定义(#475);
  • 令牌生成随机性改进(#481);
  • hash_equals()做签名验证(#480),明确标注"防止远程时序攻击(timing attacks)"——这是库安全历史上的关键提交之一,源码可见 Encryption/Jwt.php。

3.8 v1.7(2015-04-23):协议兼容与配置项细化

  • PDO 的 fetch 模式从FETCH_BOTH改为FETCH_ASSOC(#500);
  • Bearer token header 名大小写不敏感(#508);
  • validateRedirectUri改为公开方法(#512),便于子类覆写;
  • Cassandra 存储补齐PublicKeyInterfaceUserClaimsInterface(#530);
  • DynamoDB 存储修复(#505);
  • OpenID Connect 新增code id_token返回类型(#556);
  • JWT 漏洞修复(#564)——结合 1.6 的时序攻击防护,1.x 中后期对 JWT 签名的安全投入持续加码;
  • 新增unset_refresh_token_after_use配置(#571):刷新令牌使用后是否作废,该配置现为Server的默认项(默认true)。

3.9 v1.8(2015-09-18):令牌吊销与 jti 补全

  • 访问令牌载荷增加jti(#594),JwtAccessToken.php 中jtiid同值,id保留是为了向后兼容(#591 相关);
  • 修复 JWT 生命周期配置(#598);
  • 令牌吊销支持(#586):Server::handleRevokeRequest()按 RFC 7009(Token Revocation)处理/revoke端点;
  • 新增FirebaseJWT 桥接(#636),即 Encryption/FirebaseJwt.php;
  • Mongo 的 HHVM 兼容(#639)。

3.10 v1.9(2017-01-06):RFC6750 兼容与 PHP 生态适配

  • client_secret允许为 NULL(#645):为公开客户端(public client)铺路;
  • CassandraisPublicClient修复(#651);
  • 客户端 scope 限制的 bug 修复(#670);
  • 开放密码哈希算法覆写(#672):UserCredentials相关存储可自定义密码校验算法;
  • Token 响应 Content-Type 修正为application/json(#698);
  • unsetAccessToken/unsetRefreshToken保证返回布尔值(#729);
  • OpenID ConnectCodeIdToken的 UserClaims 修复(#749);
  • RFC 6750 兼容(#784):Bearer 令牌按规范处理;
  • redirect_uri_mismatch对含编码字符的 URI 修复(#776);
  • 未提供访问令牌时资源控制器返回空请求体的问题修复(#759);
  • 随机源顺序:OpenSSL 优先于 Mcrypt(#773);
  • 新增 Mongo DB 存储(#790)。

3.11 v1.10.0(2017-11-15):可定制性与现代 PHP 适配

1.10.0 是 1.x 的最终版本,也是 CHANGELOG 记录最详尽的一版:

  • 新增受保护的createPayload()方法(#795):允许子类轻松定制 JWT 载荷——JwtAccessToken.php 中该方法还支持jwt_extra_payload_callable回调注入额外声明;
  • UserInfoController构造简化(#807);
  • CORS 修复(#829):/revoke与访问令牌请求的跨域问题;
  • random_bytes优先(#834):PHP 7 起优先使用random_bytes生成令牌;
  • PHP 7.2count()错误修复(#872);
  • 测试现代化:显式引入 PHPUnit 4(#827)、改用PHPUnit\Framework\TestCase(#885)、移除 PHP 5.3 的 Travis 支持(#869)并新增 PHP 7.2(#873)。

四、安全修复专题:从 CHANGELOG 看 OAuth2 攻击面

CHANGELOG 中反复出现安全相关条目,它们构成了理解该库"为何值得信任"的重要证据链:

安全主题引入/修复版本说明
空客户端凭据授予访问1.1(#274)修复提供空client_id/client_secret即获 API 访问的问题
令牌随机性1.4(#368)、1.6(#481)、1.10(#834)mcrypt_create_iv/openssl_random_pseudo_bytes→ 更优随机 →random_bytes优先
签名验证时序攻击1.6(#480)hash_equals()做签名比较,防远程时序攻击
JWT 漏洞1.7(#564)针对 JWT 实现的漏洞修复
JWT 重放1.1(#268)、1.6(#475)、1.8(#594)jti声明 + JTI 表定义,防重放
编码字符 URI 绕过1.9(#776)redirect_uri_mismatch对编码字符的处理
RFC 6750 / 规范兼容1.9(#784)、1.5(#404)Bearer 令牌错误语义对齐 RFC
CORS1.10(#829)修复撤销与请求访问令牌的跨域问题
随机源降级1.9(#773)OpenSSL 优先,Mcrypt 仅作后备

结合 mailcow 的集成可见:mailcow 在 functions.oauth2.inc.php 中使用random_bytes(6)/random_bytes(12)生成client_idclient_secret,正是 1.10(#834)"use random_bytes if available" 思路在应用层的延续;而令牌本身由库内generateAccessToken()等实现生成。


五、JWT 访问令牌:1.x 最重要的功能主线

从 v1.1 到 v1.10,JWT 访问令牌经历了"引入 → 更名 → 规范化 → 可定制"的完整生命周期,CHANGELOG 中可串联出一条清晰的主线:

  1. v1.1(#245):引入 JSON 加密令牌(crypto token),刷新令牌生命周期修复(#253);
  2. v1.3(#318)use_crypto_tokens配置让开发者一行启用;
  3. v1.6(#437):更名为JwtAccessToken+use_jwt_access_tokens;claims 对齐规范(#460);hash_equals验证(#480);
  4. v1.7(#563)JwtAccessToken支持issuer配置键;
  5. v1.8(#594/#598):补jti、修复 JWT 生命周期配置;
  6. v1.10(#795)createPayload()开放为受保护方法,配合jwt_extra_payload_callable支持任意额外声明。

当前 JwtAccessToken.php 的构造参数揭示了这套机制的配置面:store_encrypted_token_string(是否存储完整加密串,默认true)、issuer(签发者)、access_lifetimerefresh_token_lifetime,以及 Server.php 中统一的默认配置表。mailcow 当前并未开启use_jwt_access_tokens(prerequisites.inc.php 仅配置了令牌生命周期),因此实际走的是传统的不透明令牌 + 数据库存储路径;如需切换为 JWT 自包含令牌,只需在OAuth2\Server构造时传入'use_jwt_access_tokens' => true,并配套提供实现PublicKeyInterface的公钥存储(对应Server::createDefaultJwtAccessTokenResponseType()的要求)。


六、存储层矩阵:一个 PDO 库如何覆盖六种后端

CHANGELOG 中存储层的新增节奏几乎是逐版本推进的:

  • v1.1:Redis(predis)Scope 存储范式对齐(#215);
  • v1.3:Cassandra(#311);
  • v1.4:PDO 支持 DSN 字符串、Postgres、自定义 PDO options(#189/#367/#384);
  • v1.5:DynamoDB(#399),PDO SQL 随库捆绑(#354);
  • v1.6:Couchbase(#447);
  • v1.9:Mongo DB(#790);
  • 1.x 后期:MongoDB(#790 之后 #664 相关,1.10 测试链更新)。

仓库 Storage 目录 中现存PdoRedisCassandraCouchbaseDBDynamoDBMongoMongoDBMemory等实现,印证了 CHANGELOG 的记录。OAuth2\Server$storageMap(Server.php)定义了每种存储类型对应的接口契约(access_tokenclientrefresh_tokenuser_credentialsscope等),addStorage()会按接口自动归类——这也解释了 mailcow 为什么只传一个mailcowPdo实例就能同时满足授权码、用户凭据、刷新令牌三种授权类型的存储需求。

mailcow 的oauth_clients表结构(init_db.inc.php)与库内 PDO 存储的字段约定保持一致:client_id(VARCHAR(80),主键)、client_secretredirect_uri(VARCHAR(2000))、grant_typesscope(VARCHAR(4000))、user_id,并额外以id为唯一键——后者正是 functions.oauth2.inc.php 中按id管理客户端(编辑 redirect_uri、续签密钥、删除)所依赖的。


七、OpenID Connect:1.4 引入的能力集

v1.4(#351)引入的 OpenID Connect 支持在 1.x 后续版本持续演进:

  • v1.5(#412):多 scope 的 claims 解析修复、prompt=consent/none
  • v1.6:claims 对齐规范;
  • v1.7(#556):新增code id_token返回类型;
  • v1.9(#749):CodeIdToken的 UserClaims 修复;
  • v1.10(#807):UserInfoController构造简化。

该能力集在 src/OAuth2/OpenID 下自成体系:Controller\UserInfoController处理 UserInfo 端点、ResponseType\IdToken/IdTokenToken/CodeIdToken定义返回类型、Storage\UserClaimsInterface定义用户声明存储契约。启用方式为OAuth2\Server配置use_openid_connect => true,此时 Server.php 会调用validateOpenIdConnect()校验授权码授权类型必须实现 OpenID 版本接口。


八、配置项演变:一份 Server 配置的考古

将 CHANGELOG 各版本的配置相关条目与 Server.php 的默认配置表对照,可以还原 1.x 配置面的扩充轨迹:

配置项默认值来源版本/条目
redirect_status_code1.0(#203)
use_crypto_tokensuse_jwt_access_tokensfalse1.3(#318)→ 1.6(#437)更名
access_lifetime/refresh_token_lifetime36001.1(#278)起可配置
always_issue_new_refresh_tokenfalse1.5 前后(1.10 默认表确认)
unset_refresh_token_after_usetrue1.7(#571)
issuer/store_encrypted_token_string''/true1.7(#563)/ 1.6 后(JwtAccessToken 构造)
jwt_extra_payload_callablenull1.10(#795,配套createPayload
allow_public_clients/allow_credentials_in_request_bodytrue1.9(#645 client_secret 可空)相关

mailcow 使用了其中access_lifetimerefresh_token_lifetimealways_issue_new_refresh_token三个,其余保持库默认值——这体现了"库提供完整配置面、应用按需覆盖"的集成模式。


九、从 CHANGELOG 到生产实践:mailcow 集成要点

综合 CHANGELOG 与 mailcow 源码,可以总结出几条对生产环境有直接价值的实践结论:

  1. 授权流程是两段式的validateAuthorizeRequest校验 +handleAuthorizeRequest落库,mailcow 的 authorize.php 严格遵循此模式,且支持OAUTH2_FORGET_SESSION_AFTER_LOGIN在授权后清除会话;
  2. scope 白名单化:mailcow 使用Memory存储声明default_scope=profilesupported_scopes=[profile](prerequisites.inc.php),profile.php 也仅对profilescope 返回用户信息——与库的Scope校验机制(1.3 起getDefaultScope接收client_id)配合,天然防止 scope 越权;
  3. 令牌吊销可运维化:1.8(#586)的撤销能力在 mailcow 侧体现为 functions.oauth2.inc.php 的revoke_tokens操作——删除oauth_access_tokensoauth_refresh_tokens中对应客户端的全部令牌;
  4. 升级语义:1.5(#397)refresh token 不再加密、1.3(#320)getDefaultScope签名变更均属 BC(Breaking Change),在升级自托管依赖时需对照 CHANGELOG 检查子类覆写是否受影响。

结语

一份 CHANGELOG 的价值远不止"版本记录":透过 bshaffer/oauth2-server-php 1.x 的 11 个版本,可以完整看到一套 OAuth2 服务端库如何在四年内完成从"可用"到"规范、安全、可扩展"的进化——JWT 体系的规范化、时序攻击与重放防护、六种存储后端的覆盖、OpenID Connect 的落地,以及面向 PHP 7 生态的现代化。而 mailcow 仓库中的装配代码(prerequisites.inc.php)、端点实现(oauth 目录)与表结构(init_db.inc.php)则展示了这套库在真实邮件系统里的最小化、白名单化、可运维化的落地方式。对需要在 PHP 应用中自建 OAuth2 服务的开发者而言,这份 CHANGELOG 配合其源码,本身就是一份不可多得的工程参考。

【免费下载链接】mailcow-dockerizedmailcow: dockerized - 🐮 + 🐋 = 💕项目地址: https://gitcode.com/GitHub_Trending/ma/mailcow-dockerized

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询