简介:Mangoa-Auth/芒果自助多应用企业级网站授权系统源码,是一套面向个人开发者与中小企业的PHP授权管理方案,基于原生PHP与EasyWeb框架构建,重点解决多应用授权分发、盗版入库追溯、远程更新推送等核心问题。系统支持域名、秘钥、IP、卡密多种授权方式,并可随时关闭或开启验证,便于灵活控制产品分发。内置独立用户中心,用户可自助注册、购买授权、更换授权、下载源码、升级权限,全程无需人工操作。功能涵盖自定义等级权限、多应用授权管理、在线加密解密、API对接授权、支付接口认证、第三方快捷登录、实名检测、工单反馈、短信/邮件/微信通知以及文章发布等,几乎覆盖授权平台所需常见模块。资源包共1614个文件,约13.87MB,类型以PHP后端逻辑(352个)、JS交互脚本(607个)、CSS样式(130个)为主,另含GIF/PNG图标、字体、音频及配置文本等,目录结构清晰,便于直接部署与二次开发。目前已有190人学习浏览,适合具备PHP基础、希望低成本快速搭建自主授权系统的技术人员参考使用,有助于理解授权、加密、用户体系等模块的设计思路。
1. Mangoa-Auth 正在解决哪一类烂摊子
你辛苦维护的 PHP 站点系统卖出去几百份之后,一定会遇到这种场景:授权码被复制到无关域名上也能跑通,买家把授权文件打包二次分发,旧版本用户永远不升级,而你手里只有一张记录着 QQ 号的 Excel。Mangoa-Auth 这类「自助多应用企业级网站授权系统」源码,就是把授权码签发、站点绑定、盗版识别、远程更新这四件事合成一套可以私有化部署的授权中心。它本身不是一个业务系统,而是一个独立跑在你自己服务器上的服务端源码:买家在你的授权中心注册、下单、绑定域名、领取授权码;目标网站装上配套的客户端组件后,每次启动和升级都要回来验证。
「盗版入库」这个标题关键词,落到工程实现上就是黑名单特征库:授权系统采集到未授权部署的域名、目录结构、数据库指纹之后,自动登记入库,并在后续的更新请求中拒绝服务。这篇文章会按授权链路逐层拆开:先讲授权码的签发与验签机制,再讲多应用和自助流程,然后单独剖析盗版入库和远程更新这两条核心通道,最后给出上线前值得做的几个自检动作。适合正在做付费源码分发、SaaS 私有化版本,或者要给内部多套系统统一做授权管控的团队。
2. 授权链路的核心:签发、验签与本地缓存
2.1 授权码本质是一段带签名的载荷
大多数网站授权系统源码的做法不是「服务端实时在线校验」,而是把授权码设计成一个自带签名的数据包:买家把授权码粘贴到目标站,目标站本地就能验真。这种设计的原因是目标站点可能部署在内网、离线环境,或者你的授权服务器偶尔宕机,不能因为连不上授权中心就把付费用户锁在门外。
授权码的载荷至少包含appid、domain、exp、sub四个字段。appid标识用的是哪个产品,domain绑定目标站点域名,exp是过期时间戳,sub是这条授权的唯一编号。整个载荷 JSON 做 base64 编码,再用你的私钥对编码结果做签名,最终授权码格式就是载荷.签名。客户端只有公钥,能验签但无法伪造新授权码。
这里有一个容易被忽略的设计点:载荷里一定要放nbf(not before)字段,即授权生效时间。自助购买场景里用户可能付完款马上激活,而服务器时钟和用户本地时钟有偏差,没有nbf会导致验签通过但时间判断飘忽。
2.2 签发侧:PHP 私钥签名的最小实现
以 PHP 作为授权中心源码为例,签发一段授权码只需要 openssl 扩展:
<?php // issue_license.php $appId = $_POST['appid']; // 应用ID $domain = $_POST['domain']; // 绑定域名 $days = (int)$_POST['days']; // 授权时长 $payload = [ 'appid' => $appId, 'domain' => $domain, 'sub' => uniqid('lic_', true), 'iat' => time(), 'nbf' => time() - 300, // 允许5分钟时钟偏移 'exp' => time() + $days * 86400, 'plan' => 'pro', ]; $data = base64_encode(json_encode($payload)); $signature = ''; openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256); $license = $data . '.' . base64_encode($signature); echo $license;逻辑说明:openssl_sign对 base64 编码后的 JSON 做 SHA256 签名,签名结果再 base64 一次拼在点号后面。第二步把私钥换成公钥即可验签。需要注意time() + $days * 86400是秒级时间戳,很多从 Java 或 Python 转过来的开发者容易误写成毫秒,导致授权码一签发就是过期状态。
客户端验签时先拆载荷和签名,openssl_verify返回 1 表示签名有效,0 表示无效,-1 表示证书错误。验签通过后再逐项检查appid是否匹配、domain是否是当前域名、exp是否大于当前时间。任何时候都先验签再判断业务条件,顺序反过来会让伪造授权码成为可能。
2.3 本地缓存与离线策略参数
继续讲客户端侧。每次启动都请求授权中心不现实,常见做法是把验签结果缓存到本地文件,同时记录last_check时间戳:
| 参数 | 建议值 | 说明 |
|---|---|---|
| cache_ttl | 7 天 | 缓存有效期,超过后必须在线验证 |
| offline_grace | 2 天 | 授权中心连不上时的宽限期 |
| clock_skew | 5 分钟 | 校验 exp 时允许的时钟偏移 |
| max_domain_bind | 1 | 授权码允许绑定的域名数 |
缓存文件建议用站点目录外的路径存放,比如 Linux 下的/var/lib/mangoa/或 Windows 下的ProgramData,避免更新程序覆盖源码时把授权缓存一起清掉。删除缓存文件后的冷启动应该强制走一次在线验证,否则盗版使用者会通过删缓存反复刷新离线宽限。
3. 多应用与自助:应用池、授权表与换绑流程
3.1 应用池模型:一个授权中心挂多个产品
标题里的「多应用」指的是授权中心可以同时管理多个不同产品的授权,每个产品对应一个appid,各自持有独立的密钥对和更新通道。这样做的好处是:给产品 A 发授权不会泄露产品 B 的验签公钥,产品 A 停止销售也不影响 B 的授权与更新。如果授权中心本身是二次开发而来的 PHP 源码,应用池模型还能让你把客户的定制版作为一个独立appid挂进去,定制版和标准版互不干扰。
实际落地时,密钥对不要存在数据库里,而是以文件方式放在 Web 根目录之外的目录,数据库只存公钥。原因很实际:这套源码大概率是 PHP 源码形态交付,数据库可能被拖库,但只要私钥不在库里,攻击者依然无法伪造授权码。
如果你打算把授权中心后台做成自助式的运营面板,基于 fastadmin 这类 PHP 后台框架来改造是最常见的出发点,公共模型已经解决登录权限、操作日志、菜单权限,你只需要新增应用管理、授权订单、盗版名单三块业务表。
3.2 核心表结构:应用、授权、日志
一张apps表维护应用池,一张licenses表维护所有已签发授权,一张auth_logs表记录激活与心跳事件:
CREATE TABLE apps ( appid VARCHAR(64) PRIMARY KEY, app_name VARCHAR(128) NOT NULL, pub_key TEXT NOT NULL, update_channel VARCHAR(16) DEFAULT 'stable', created_at INT NOT NULL ); CREATE TABLE licenses ( id INT AUTO_INCREMENT PRIMARY KEY, appid VARCHAR(64) NOT NULL, customer VARCHAR(128) NOT NULL, domain VARCHAR(255) NOT NULL, lic_code TEXT NOT NULL, status ENUM('active','suspended','expired') DEFAULT 'active', bind_count INT DEFAULT 0, expires_at INT NOT NULL, created_at INT NOT NULL, INDEX idx_appid (appid), INDEX idx_status (status) ); CREATE TABLE auth_logs ( id BIGINT AUTO_INCREMENT PRIMARY KEY, license_id INT NOT NULL, event VARCHAR(32) NOT NULL, ip VARCHAR(45) NOT NULL, machine_fp VARCHAR(64) DEFAULT '', created_at INT NOT NULL, INDEX idx_lic (license_id), INDEX idx_event (event) );逻辑说明:bind_count用于记录当前已绑定的域名/机器数,machine_fp是客户端上报的机器指纹,用于识别同一授权码是否被部署到多台机器。auth_logs里的event字段建议使用枚举值:issue(签发)、activate(激活)、checkin(心跳)、suspend(挂起)、tamper(篡改)。
3.3 自助换绑的时序与幂等
自助通道的完整流程是:买家注册登录 → 选择应用 → 提交要绑定的域名 → 系统生成授权码 → 买家把授权码粘贴到目标站 → 目标站请求激活接口 → 激活成功进入心跳模式。
换绑是最容易出错的一个环节,因为「解绑旧域名」和「绑定新域名」不是原子操作。常见做法是把换绑设计成两阶段:买家发起换绑时,授权码先进入suspended状态,旧域名立即失效,新域名在 24 小时内激活有效。这样即使买家在换绑过程中反悔,旧域名也不能继续使用,避免「换绑=无限复制」的漏洞。
自助下单接口要有幂等控制,同一个订单号重复提交不能签发多张授权码。最简单的方式是在订单表上给order_no加唯一索引,签发授权码和更新订单状态放在一个数据库事务里完成,任何一步失败都整体回滚。
3.4 参数与配额:按产品维度配置
| 配置项 | 建议范围 | 说明 |
|---|---|---|
| 单授权码绑定数 | 1-3 | 超过即视为可疑并发部署 |
| 换绑冷却时长 | 24 小时 | 冷却期内新绑定不生效 |
| 月换绑次数上限 | 2 次 | 超出进入人工审核 |
| 心跳间隔 | 12-24 小时 | 间隔越短盗版发现越早,但负载越高 |
心跳间隔这个参数容易被忽略:太短会让授权中心变成流量瓶颈,太长则盗版站点能存活很久不被发现。以单台普通云服务器跑 PHP 授权中心为例,每秒处理 50 次验签请求没有压力,按 24 小时间隔算,支撑上万活跃站点没问题。
4. 盗版入库:特征采集、指纹比对与自动封禁
4.1 入库判定:先立规则再谈识别
「盗版入库」不是 AI 玄学,而是一套基于特征指纹和行为日志的判定规则。判定的前提是授权中心已经掌握正版部署的画像:正版站点激活时会上报域名、目录结构、数据库表前缀、源码版本号、机器指纹;之后每次心跳和更新请求都会带上这些信息。当授权中心发现某个请求的来源特征与正版画像偏离过大,或者根本没有对应的有效授权码,就可以把该站点记入盗版特征库。
常见判定规则有四条,优先级从高到低:第一,授权码校验失败的更新请求累计达到 3 次;第二,请求中携带的机器指纹与激活时不一致且无换绑记录;第三,数据库表前缀、目录路径与正版安装包不符;第四,版本号高于当前官方最新版——说明对方自己改了源码或接了非官方渠道。任何一条命中,都先进入观察状态,而不是直接封禁,原因很简单,换服务器搬家、用户自己改目录名都会触发误报。
4.2 客户端指纹采集与上报
客户端组件在每次心跳和更新请求时,把指纹放在自定义 Header 里带上来:
<?php // client/fingerprint.php public function collectFingerprint(): array { return [ 'domain' => $_SERVER['HTTP_HOST'] ?? '', 'install' => str_replace($_SERVER['DOCUMENT_ROOT'], '', dirname(__DIR__)), 'db_pref' => defined('DB_PREFIX') ? DB_PREFIX : 'unknown', 'version' => self::VERSION, 'machine' => hash('sha256', gethostname() . filemtime(__FILE__)), 'watermark' => $this->loadWatermark(), ]; } // 上报时拼接为单字符串 $fpRaw = json_encode($this->collectFingerprint()); $fp = base64_encode($fpRaw); // 请求头: X-Mango-Fp: <fp>逻辑说明:filemtime(__FILE__)采集客户端文件的修改时间,正常情况下正版安装包所有文件时间一致;盗版使用者改动验证逻辑后,这个时间必然变化。watermark是安装时写入的随机水印值,正版部署的水印值在授权中心有记录,盗版迁移后水印值对不上。
这条上报通道要轻量,一次心跳只增加几百字节流量。指纹只在授权中心侧做比对,不要在客户端做任何「判断自己是否为盗版」的逻辑,本地判断结果毫无意义。
4.3 从观察到封禁的状态流转
盗版名单管理是一张状态机表,包含四个状态:
| 状态 | 触发条件 | 后续动作 |
|---|---|---|
| fresh | 首次上报,无任何异常 | 记录指纹,标记观察 |
| suspect | 指纹异常或授权无效 | 进入 7 天观察窗口 |
| confirmed | 观察期内再次异常 | 加入盗版库,停止更新 |
| whitelisted | 人工确认为误报 | 清除记录,恢复正常 |
封禁动作要注意一个细节:对已确认的盗版部署,更新接口不要直接返回 403 或空包,而是返回「版本已是最新」的假性响应。直接拒绝会让对方立刻意识到被发现,转而手动离线破解;假性响应能让对方一直停留在旧版本,累积的安全漏洞和功能性 Bug 会持续消耗对方的维护成本。
入库记录至少包含域名、IP、机器指纹、首次发现时间、命中规则。IP 会变,域名会换,机器指纹相对稳定,所以黑名单索引应该建在machine_fp上。
5. 远程更新:版本协议、增量包与回滚锁
5.1 版本检查协议:一次带校验的请求
远程更新是授权系统的增值通道,也是盗版入库的关键数据来源。客户端每次更新前先调用版本检查接口,带上appid、version、license和指纹 Header。授权中心先验授权,再审指纹,最后决定返回什么内容:
{ "latest": "2.4.0", "current": "2.3.1", "update_type": "incremental", "incremental": { "base": "2.3.1", "target": "2.4.0", "files": [ { "p": "app/controller/Auth.php", "md5": "a1b2c3d4e5", "size": 4096 }, { "p": "config/license.php", "md5": "9f8e7d6c5b", "size": 1024 } ] }, "notify": "建议尽快更新,修复授权校验绕过漏洞" }接口路径建议同时支持GET和POST:GET用于快速版本比对,POST带上完整指纹用于更新包下发。返回中的update_type区分incremental和full,增量更新失败时客户端可以请求全量包兜底。
5.2 增量更新:按文件树比对 MD5
增量包的核心是一个文件清单,每个文件路径带 MD5 和大小。客户端下载单个文件后先校验 MD5,再覆盖到本地对应路径:
# client/update.sh BASE_URL="https://auth.example.com/dl" TARGET_LIST="$(jq -r '.incremental.files[] | .md5 + " " + .p' update.json)" while read -r md5 path; do tmp="/tmp/mangoa_$(basename "$path")" curl -fsS "${BASE_URL}/${md5}" -o "$tmp" || exit 1 # 校验下载文件完整性 if [ "$(md5sum "$tmp" | cut -d' ' -f1)" != "$md5" ]; then echo "checksum failed: $path" exit 1 fi # 保留一分备份,最多留3个历史版本 backup_dir="backup/$(date +%s)" mkdir -p "$backup_dir" cp "$path" "$backup_dir/" 2>/dev/null || true cp -f "$tmp" "$path" done <<< "$TARGET_LIST"逻辑说明:每个文件用其 MD5 值作为下载文件名,天然具备内容寻址能力,同一个文件多个版本复用同一份存储,不会被重复下载。backup_dir用时间戳区分版本,保留最近 3 份历史备份,方便回滚。这里的update.json是版本检查接口返回的响应体,建议先完整落盘再解析,避免网络中断导致半截 JSON 被当成完整清单。
关键参数是文件清单的粒度:p必须是相对站点根目录的路径,禁止使用绝对路径,否则换服务器后路径失效。另外,不要在清单里直接暴露服务器磁盘绝对路径,那等于把目录结构送给攻击者。
5.3 下载签名与回滚锁
增量文件默认放在一个公开目录,但这不意味着任何拿到 URL 的人都能下载。常见做法是下载 URL 上附带一个 HMAC 签名和过期时间:
https://auth.example.com/dl/{md5}?exp=1720000000&sig=8f3a...签名内容由md5 + exp拼接后用服务器密钥做 HMAC-SHA256,客户端只需用常规 HTTP 客户端就能下载,不依赖特殊工具。过期时间默认 10 分钟,避免下载链接被收集后长期有效。
回滚锁的作用是防止更新到一半时用户手动中断,导致文件半新半旧。实现方式是在更新开始前写一个.update.lock文件,记录目标版本号;更新完成后删除。客户端启动时如果发现锁文件存在、目标版本和当前版本不一致,就说明上次更新中断,自动从备份目录恢复。
6. 上线前必做的三个自检
6.1 抓一次完整激活链路
搭建好授权中心源码后,先别急着接业务,用 curl 手工跑一遍完整流程:调用签发接口生成授权码、在测试站点上请求激活、模拟心跳请求。校验点有三个:auth_logs表是否按issue → activate → checkin顺序落库;激活成功后第二次请求是否命中缓存没有重复写库;授权码过期后是否准确返回 401 响应。任何一个环节不符合预期,都要在接真实用户前修复。
6.2 实测三类绕过路径
自己搭一套目标站,按攻击者思路做边界测试。第一类,修改服务器时间跳过exp:把系统时间拨到授权过期后一年,观察客户端是否在时钟偏移容差内继续放行。第二类,删除本地缓存文件后冷启动:如果系统直接放行没有强制走在线验证,说明冷启动逻辑存在缺口。第三类,把整套站点代码复制到另一台机器上运行:机器指纹变化且没有换绑记录时,授权中心是否成功捕获异常并产生suspect记录。
这三条路径测完,授权系统源码的核心防线是否成立基本就清楚了。
6.3 验证盗版入库的封禁响应
在授权中心数据库里手动插入一条测试域名记录,标记为confirmed状态,然后从该域名发起一次更新请求。期望行为是:更新接口返回假性「已是最新」响应,auth_logs里生成一条tamper事件,该域名的下载请求在服务端日志中可溯源。如果返回了真实的更新包或者直接报错,说明封禁分支没有生效,需要检查更新接口的鉴权顺序。跑通这一步,盗版入库从识别、登记到封禁的闭环才算真正闭合。
本文还有配套的精品资源,点击获取