自发布插件更新检查器库以来,提出的最常见问题之一是:“如何保护我的下载链接,以便只有购买了我的插件的用户才能下载更新?”
在本文中,我将尝试在使用WP Update Server提供插件更新的上下文中回答该问题。我还将从我的一个商业插件中提供一些实际示例。
让我们开始吧。有许多方法可以确保更新下载的安全,但是大多数方法可以归结为:
- 给每个用户某种安全令牌。这可以是会员站点的登录名+密码,许可证密钥,或者更深奥的东西。
- 每当有人在其站点上安装您的插件时,都要求他们输入密钥/登录/以其他方式输入。
- 修改插件以将令牌附加到每个更新请求。
- 编写一个服务器脚本,该脚本在允许下载之前验证令牌。
选择一个安全令牌
实施前两个步骤的方式会因插件UI以及所使用的在线商店,购物车软件或成员资格插件而有很大差异。您可能已经存在某种客户身份验证机制,只需要进行一些调整即可用于更新,或者您可能需要从头开始构建自己的身份验证机制。这里没有“一刀切”的解决方案。
就个人而言,我更喜欢使用许可证密钥。每当有人购买我的Admin Menu Editor Pro插件时,订单处理脚本都会生成一个随机密钥,将其存储在数据库中,然后将密钥和下载链接发送到客户的电子邮件中。然后,当他们安装插件时,将提供一个输入许可证密钥的链接。
我不会在此处包括许可证管理代码,因为它不在本文的讨论范围内,而是为该特定插件构建的,但是用户界面看起来像这样(单击放大):


将令牌添加到更新请求
现在,我们如何将安全令牌添加到每个更新请求中?您可以使用addQueryArgFilter($callback)
更新检查器的方法来执行此操作。回调函数将接收查询参数的关联数组。只需将令牌添加到列表中并返回修改后的数组即可。
这是一个例子:
/* ... Code that initializes the update checker ... */
//Add the license key to query arguments.
$updateChecker->addQueryArgFilter('wsh_filter_update_checks');
function wsh_filter_update_checks($queryArgs) {
$settings = get_option('my_plugin_settings');
if ( !empty($settings['license_key']) ) {
$queryArgs['license_key'] = $settings['license_key'];
}
return $queryArgs;
}
使用令牌授权下载
最后,让更新服务器在允许用户下载更新之前验证安全令牌。为此,您需要创建一个自定义服务器类(请参阅扩展服务器)并至少重写该
Wpup_UpdateServer::checkAuthorization($request)
方法。这是使用此方法应做的事情:
- 通过使用检索包含令牌的查询参数
$request->param('arg_name')
。 - 验证令牌。同样,这部分取决于您。您可以在数据库中查找它,使用校验和来验证它,或进行其他操作。
- 如果令牌是好的,则无需执行任何特殊操作。
- 如果令牌无效,请调用
$this->exitWithError('Error message')
以输出错误并停止脚本执行。
以下是脚本的简化版本,用于为Admin Menu Editor Pro实施安全更新 。它比上面的概述要先进一些,但是总体思路是相同的。
(同样,许可证管理超出了本文的范围,因此,我省略了大多数与加载和验证许可证有关的代码。只需将verifyLicenseExists()和其他许可功能视为伪代码。)
class SecureUpdateServer extends Wpup_UpdateServer {
protected $licenseServer;
public function __construct($serverUrl, $licenseServer) {
parent::__construct($serverUrl);
$this->licenseServer = $licenseServer;
}
protected function initRequest($query = null, $headers = null) {
$request = parent::initRequest($query, $headers);
//Load the license, if any.
$license = null;
if ( $request->param('license_key') ) {
$result = $this->licenseServer->verifyLicenseExists(
$request->slug,
$request->param('license_key')
);
if ( is_wp_error($result) ) {
//If the license doesn't exist, we'll output an invalid dummy license.
$license = new Wslm_ProductLicense(array(
'status' => $result->get_error_code(),
'error' => array(
'code' => $result->get_error_code(),
'message' => $result->get_error_message(),
),
));
} else {
$license = $result;
}
}
$request->license = $license;
return $request;
}
protected function filterMetadata($meta, $request) {
$meta = parent::filterMetadata($meta, $request);
//Include license information in the update metadata. This saves an HTTP request
//or two since the plugin doesn't need to explicitly fetch license details.
$license = $request->license;
if ( $license !== null ) {
$meta['license'] = $this->licenseServer->prepareLicenseForOutput($license);
}
//Only include the download URL if the license is valid.
if ( $license && $license->isValid() ) {
//Append the license key or to the download URL.
$args = array( 'license_key' => $request->param('license_key') );
$meta['download_url'] = self::addQueryArg($args, $meta['download_url']);
} else {
//No license = no download link.
unset($meta['download_url']);
}
return $meta;
}
protected function checkAuthorization($request) {
parent::checkAuthorization($request);
//Prevent download if the user doesn't have a valid license.
$license = $request->license;
if ( $request->action === 'download' && ! ($license && $license->isValid()) ) {
if ( !isset($license) ) {
$message = 'You must provide a license key to download this plugin.';
} else {
$error = $license->get('error');
$message = isset($error) ? $error : 'Sorry, your license is not valid.';
}
$this->exitWithError($message, 403);
}
}
}
来源于: https://w-shadow.com/blog/2013/03/19/plugin-updates-securing-download-links/