PAYATHON 2026

如何通过 ASP.NET 网页获取并更新 Plesk10 数据?

支付小周

结论

可以通过 ASP.NET 在服务器端调用 Plesk 10 的 XML API RPC,读取订阅、邮箱、FTP 账户及资源使用量,也可以将允许的修改提交给 Plesk。

实施前需要注意以下两点:

  • 不要把 Plesk 管理接口直接暴露给浏览器。ASP.NET 应作为中间层,处理身份验证、权限校验和 API 调用。
  • BILLING、INVOICE 和付款记录通常不是 Plesk Panel 的核心数据。如果服务商使用 Customer & Business Manager、旧版 Plesk Billing 或第三方财务系统,还需要接入相应系统的 API 或数据库接口。

另外,拥有 reseller hosting plan 并不代表一定拥有远程 API 权限。主机服务商可能禁止 reseller 使用 XML API,也可能只允许其操作名下的订阅。开始开发前,应先向服务商确认权限范围。

推荐架构

可以采用下面的调用结构:

用户浏览器
    ↓ HTTPS
ASP.NET 网站
    ├── 验证当前用户
    ├── 查找用户对应的 Plesk subscription/webspace ID
    ├── 检查邮箱、FTP 等资源是否属于该用户
    └── 调用 Plesk XML API
            ↓ HTTPS 8443
        Plesk 10

不要让前端 JavaScript 直接连接以下地址:

https://plesk.example.com:8443/enterprise/control/agent.php

否则,Plesk 管理员或 reseller 的凭据会暴露给终端用户。

调用 Plesk XML API

Plesk 10 的 XML API RPC 通常位于:

https://plesk.example.com:8443/enterprise/control/agent.php

客户端通过 POST 发送 XML,并在 HTTP 请求头中提供 Plesk 登录凭据:

HTTP_AUTH_LOGIN: reseller-name
HTTP_AUTH_PASSWD: reseller-password
Content-Type: text/xml

部分安装环境也支持使用 Plesk 生成的 secret key,并通过 KEY 请求头完成认证。该方式能否使用,取决于服务器配置和当前账户的权限。

ASP.NET 调用封装

下面的示例适用于现代 ASP.NET 和 ASP.NET Core。旧版 ASP.NET Framework 也可以沿用相同的 HttpClient 调用思路。

using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;

public sealed class PleskApiClient
{
    private readonly HttpClient _httpClient;
    private readonly string _login;
    private readonly string _password;

    public PleskApiClient(
        HttpClient httpClient,
        string endpoint,
        string login,
        string password)
    {
        _httpClient = httpClient;
        _httpClient.BaseAddress = new Uri(endpoint);

        _login = login;
        _password = password;
    }

    public async Task<XDocument> SendAsync(
        XDocument packet,
        CancellationToken cancellationToken = default)
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, "");

        request.Headers.TryAddWithoutValidation("HTTP_AUTH_LOGIN", _login);
        request.Headers.TryAddWithoutValidation("HTTP_AUTH_PASSWD", _password);
        request.Headers.TryAddWithoutValidation("HTTP_PRETTY_PRINT", "TRUE");

        request.Content = new StringContent(
            packet.ToString(SaveOptions.DisableFormatting),
            Encoding.UTF8,
            "text/xml");

        using var response = await _httpClient.SendAsync(
            request,
            cancellationToken);

        string responseXml = await response.Content.ReadAsStringAsync(
            cancellationToken);

        response.EnsureSuccessStatusCode();

        var document = XDocument.Parse(responseXml);

        // Plesk 有时会在 HTTP 200 响应中返回业务级错误,
        // 因而不能只检查 HTTP 状态码。
        var failedResult = document
            .Descendants("result")
            .FirstOrDefault(result =>
                string.Equals(
                    (string?)result.Element("status"),
                    "error",
                    StringComparison.OrdinalIgnoreCase));

        if (failedResult != null)
        {
            string code = (string?)failedResult.Element("errcode") ?? "unknown";
            string message =
                (string?)failedResult.Element("errtext") ??
                "Unknown Plesk API error";

            throw new InvalidOperationException(
                $"Plesk API error {code}: {message}");
        }

        return document;
    }
}

注册客户端时,应从安全配置中读取凭据,不要将密码直接写入源代码:

builder.Services.AddHttpClient<PleskApiClient>();

实际项目还需要设置连接超时,例如:

builder.Services.AddHttpClient("Plesk", client =>
{
    client.Timeout = TimeSpan.FromSeconds(30);
});

查询订阅和空间使用量

在 Plesk 10 中,首先要确认当前网站用户对应的 Plesk 客户、订阅或 webspace。不要根据用户提交的域名直接执行操作,而应在自己的数据库中保存明确的映射关系,例如:

ApplicationUserId → PleskCustomerId → PleskWebspaceId

获取 webspace 信息和统计数据时,可以使用类似下面的 XML packet:

<?xml version="1.0" encoding="UTF-8"?>
<packet version="1.6.3.0">
  <webspace>
    <get>
      <filter>
        <id>123</id>
      </filter>
      <dataset>
        <gen_info />
        <hosting />
        <stat />
      </dataset>
    </get>
  </webspace>
</packet>

在 ASP.NET 中,可以用以下方式安全地构造 XML:

var packet = new XDocument(
    new XElement("packet",
        new XAttribute("version", "1.6.3.0"),
        new XElement("webspace",
            new XElement("get",
                new XElement("filter",
                    new XElement("id", webspaceId)),
                new XElement("dataset",
                    new XElement("gen_info"),
                    new XElement("hosting"),
                    new XElement("stat"))))));

XDocument response = await pleskClient.SendAsync(packet);

统计响应可能包含磁盘、流量和邮件等数据,具体字段会受到 Plesk 10 小版本、操作系统及 XML API 协议版本的影响。磁盘统计也未必是实时计算结果,有可能来自 Plesk 最近一次运行的统计任务。

部分 Plesk 10 环境使用 domain operator,较新的协议则使用 webspace operator。应先查询服务器支持的协议版本,再参照与该服务器匹配的 API Reference。不能假定示例中的 1.6.3.0 适用于所有 Plesk 10 安装。

创建邮箱

创建邮箱的请求通常采用以下结构:

<?xml version="1.0" encoding="UTF-8"?>
<packet version="1.6.3.0">
  <mail>
    <create>
      <filter>
        <site-id>123</site-id>
        <mailname>
          <name>sales</name>
          <mailbox>
            <enabled>true</enabled>
            <quota>104857600</quota>
          </mailbox>
          <password type="plain">A-Strong-Password</password>
        </mailname>
      </filter>
    </create>
  </mail>
</packet>

site-id 必须由服务器端根据当前登录用户查询,不能直接接受浏览器传入的值。quota 通常以字节为单位,但仍需核对相应协议版本中的字段定义。

ASP.NET 构造示例:

var packet = new XDocument(
    new XElement("packet",
        new XAttribute("version", "1.6.3.0"),
        new XElement("mail",
            new XElement("create",
                new XElement("filter",
                    new XElement("site-id", webspaceId),
                    new XElement("mailname",
                        new XElement("name", localPart),
                        new XElement("mailbox",
                            new XElement("enabled", "true"),
                            new XElement("quota", quotaBytes)),
                        new XElement("password",
                            new XAttribute("type", "plain"),
                            password)))))));

await pleskClient.SendAsync(packet);

创建邮箱前还要检查:

  • localPart 是否符合邮箱名称规则;
  • 用户是否已经达到 hosting plan 允许的邮箱数量上限;
  • 配额是否超出订阅允许值;
  • 密码是否符合服务器的密码强度策略;
  • 是否已经存在同名邮箱。

删除邮箱或修改密码

删除邮箱一般通过 mail/remove 完成:

<packet version="1.6.3.0">
  <mail>
    <remove>
      <filter>
        <site-id>123</site-id>
        <name>sales</name>
      </filter>
    </remove>
  </mail>
</packet>

修改邮箱密码通常使用 mail/set,结构可以参考下面的示例:

<packet version="1.6.3.0">
  <mail>
    <set>
      <filter>
        <site-id>123</site-id>
        <mailname>
          <name>sales</name>
          <password type="plain">New-Strong-Password</password>
        </mailname>
      </filter>
    </set>
  </mail>
</packet>

不同 Plesk 10 小版本对 set packet 的元素结构可能有不同要求,需要按照服务器实际支持的 XML API schema 进行调整。

执行删除操作前,必须再次确认该邮箱属于当前用户的 webspace。可以增加二次确认,并记录操作者、邮箱地址、操作时间及 Plesk 返回结果。

创建或修改 FTP 账户

如果协议版本支持 ftp-user operator,可以使用类似下面的请求:

<packet version="1.6.3.0">
  <ftp-user>
    <add>
      <name>customer_ftp</name>
      <password>Strong-FTP-Password</password>
      <home>/httpdocs/customer</home>
      <webspace-id>123</webspace-id>
    </add>
  </ftp-user>
</packet>

修改一般使用 ftp-user/set,删除使用 ftp-user/remove。过滤器和属性的具体名称,应以目标 Plesk 服务器对应的 API Reference 为准。

实现 FTP 管理时,需要严格限制以下内容:

  • FTP 主目录必须位于该订阅允许的目录内;
  • 不能让用户通过 .. 或路径编码跳出指定目录;
  • 用户不能修改其他订阅的 FTP 账户;
  • 登录名可能受到服务器前缀、长度和字符规则的限制;
  • 部分 hosting plan 不允许创建额外的 FTP 用户;
  • 能否修改主系统 FTP 用户,取决于账户权限和服务器配置。

不要通过字符串拼接生成 XML。使用 XElement 可以避免 XML 特殊字符引起格式错误或注入问题。

Billing、Invoice 和付款数据

Plesk Panel 中的 hosting subscription 与财务系统里的收费订阅不是同一类数据。Plesk 核心 XML API 主要管理服务器对象,未必能提供完整的发票、付款及交易记录。

需要先确认服务商使用的是哪套系统:

  1. 使用 Plesk Customer & Business Manager 或旧版 Plesk Billing 时,需要查询该组件对应版本的 API。
  2. 使用 WHMCS、HostBill、ClientExec 等第三方系统时,应调用相应的第三方 API。
  3. 如果发票由主机服务商统一管理,而 reseller 账户没有财务 API 权限,则需要向服务商申请接口。
  4. 如果财务数据来自自建系统,应由自建数据库提供。Plesk 只负责邮件、网站和 FTP 等资源配置。

不建议直接读取 Plesk 内部数据库。内部表结构不是稳定的公共接口,升级后可能发生变化。直接写入数据库还可能绕过 Plesk 自身的校验、事件处理和配置同步机制。

权限设计

安全上的基本原则是,终端用户只能操作与自己绑定的 Plesk 对象。

例如,网页收到下面的请求:

{
  "webspaceId": 123,
  "mailbox": "sales",
  "newPassword": "..."
}

服务器不能因为请求中包含 webspaceId 就直接执行操作。应先检查该 ID 是否属于当前用户:

var ownedWebspaceId = await ownershipRepository
    .GetWebspaceIdForUserAsync(currentUser.Id);

if (ownedWebspaceId != request.WebspaceId)
{
    return Results.Forbid();
}

更稳妥的方式是,不允许前端传递 webspaceId,而是由服务器根据当前用户查询:

var webspaceId = await ownershipRepository
    .GetWebspaceIdForUserAsync(currentUser.Id);

await mailService.ChangePasswordAsync(
    webspaceId,
    request.Mailbox,
    request.NewPassword);

此外,还应落实以下措施:

  • 将 Plesk 凭据保存在服务器端的 secret store 或受保护配置中;
  • ASP.NET 页面必须使用 HTTPS;
  • API endpoint 应使用有效的 TLS 证书;
  • 不要为了连接旧服务器而全局关闭证书验证;
  • 不要把密码写入日志、异常页面或审计详情;
  • 对创建、修改密码和删除操作设置速率限制;
  • 为所有修改操作保留审计记录;
  • 删除邮箱等不可逆操作应增加确认步骤;
  • 单独处理 Plesk 请求超时、重复请求和部分成功等情况。

建议的实施顺序

可以先开发一个只读原型,再逐步加入修改功能:

  1. 验证 reseller 账户能否连接 XML API。
  2. 查询该 reseller 可以看到的客户和订阅。
  3. 建立网站用户与 Plesk webspace ID 的映射。
  4. 读取邮箱列表和空间统计信息。
  5. 加入邮箱创建、密码修改和删除功能。
  6. 加入 FTP 管理,并严格限制主目录。
  7. 最后接入独立的 billing 系统。

Plesk API 经常在正常的 HTTP 响应中返回业务错误,因此还必须检查:

<status>error</status>
<errcode>...</errcode>
<errtext>...</errtext>

不能仅凭 200 OK 判断操作已经成功。

参考资料

可以查阅以下 Plesk XML API RPC 文档:

当前在线文档主要针对较新的 Plesk 版本。集成 Plesk 10 时,应优先使用与服务器具体小版本对应的归档文档。XML packet 需要先在测试订阅中验证,再开放给终端用户。

备注:内容仅供参考。