.NET 部署 多域名 Https(SSL)通过代码方式
<p> 在上一个文章中,传送门,给大家介绍了怎么在配置文件中使用 Kestrel 部署 Https,正好今天有小伙伴稳问到:可以通过代码的方式实现 Kestrel 的 Https 的部署吗?答案是肯定的,我们这次一样去不是多个域名。</p><p> 在使用代码实现中,我是主要使用到 ListenOptions.UseHttps,我们先看看官方文档怎么说吧,不想看我的可以直接跳转到官方文档,传送门</p>
<hr>
<p> </p>
<p id="listenoptionsusehttps-1" class="heading-anchor"><strong><span style="font-size: 18px">ListenOptions.UseHttps</span></strong></p>
<p>将 Kestrel 配置为使用 HTTPS。</p>
<p><code>ListenOptions.UseHttps</code> 扩展:</p>
<ul>
<li><code>UseHttps</code>:将 Kestrel 配置为使用 HTTPS,采用默认证书。 如果没有配置默认证书,则会引发异常。</li>
<li><code>UseHttps(string fileName)</code></li>
<li><code>UseHttps(string fileName, string password)</code></li>
<li><code>UseHttps(string fileName, string password, Action<HttpsConnectionAdapterOptions> configureOptions)</code></li>
<li><code>UseHttps(StoreName storeName, string subject)</code></li>
<li><code>UseHttps(StoreName storeName, string subject, bool allowInvalid)</code></li>
<li><code>UseHttps(StoreName storeName, string subject, bool allowInvalid, StoreLocation location)</code></li>
<li><code>UseHttps(StoreName storeName, string subject, bool allowInvalid, StoreLocation location, Action<HttpsConnectionAdapterOptions> configureOptions)</code></li>
<li><code>UseHttps(X509Certificate2 serverCertificate)</code></li>
<li><code>UseHttps(X509Certificate2 serverCertificate, Action<HttpsConnectionAdapterOptions> configureOptions)</code></li>
<li><code>UseHttps(Action<HttpsConnectionAdapterOptions> configureOptions)</code></li>
</ul>
<p><code>ListenOptions.UseHttps</code> 参数:</p>
<ul>
<li><code>filename</code> 是证书文件的路径和文件名,关联包含应用内容文件的目录。</li>
<li><code>password</code> 是访问 X.509 证书数据所需的密码。</li>
<li><code>configureOptions</code> 是配置 <code>HttpsConnectionAdapterOptions</code> 的 <code>Action</code>。 返回 <code>ListenOptions</code>。</li>
<li><code>storeName</code> 是从中加载证书的证书存储。</li>
<li><code>subject</code> 是证书的主题名称。</li>
<li><code>allowInvalid</code> 指示是否存在需要留意的无效证书,例如自签名证书。</li>
<li><code>location</code> 是从中加载证书的存储位置。</li>
<li><code>serverCertificate</code> 是 X.509 证书。</li>
</ul>
<p>在生产中,必须显式配置 HTTPS。 至少必须提供默认证书。</p>
<p>下面要描述的支持的配置:</p>
<ul>
<li>无配置</li>
<li>从配置中替换默认证书</li>
<li>更改代码中的默认值</li>
</ul>
<p id="no-configuration-1" class="heading-anchor"><strong><span style="font-size: 18px">无配置</span></strong></p>
<p>Kestrel 在 <code>http://localhost:5000</code> 和 <code>https://localhost:5001</code> 上进行侦听(如果默认证书可用)。</p>
<p id="replace-the-default-certificate-from-configuration-1" class="heading-anchor"><strong><span style="font-size: 18px">从配置中替换默认证书</span></strong></p>
<p>Kestrel 可以使用默认 HTTPS 应用设置配置架构。 从磁盘上的文件或从证书存储中配置多个终结点,包括要使用的 URL 和证书。</p>
<p>架构的注意事项:</p>
<ul>
<li>终结点的名称不区分大小写。 例如,由于再也无法解析标识符“Families”,因此 <code>HTTPS</code> and <code>Https</code> 是等效的。</li>
<li>每个终结点都要具备 <code>Url</code> 参数。 此参数的格式和顶层 <code>Urls</code> 配置参数一样,只不过它只能有单个值。</li>
<li>这些终结点不会添加进顶层 <code>Urls</code> 配置中定义的终结点,而是替换它们。 通过 <code>Listen</code> 在代码中定义的终结点与在配置节中定义的终结点相累积。</li>
<li><code>Certificate</code> 部分是可选的。 如果未指定 <code>Certificate</code> 部分,则使用 <code>Certificates:Default</code> 中定义的默认值。 如果没有可用的默认值,则使用开发证书。 如果没有默认值,且开发证书不存在,则服务器将引发异常,并且无法启动。</li>
<li><code>Certificate</code> 部分支持多个证书源。</li>
<li>只要不会导致端口冲突,就能在配置中定义任何数量的终结点。</li>
</ul>
<p id="certificate-sources-1" class="heading-anchor"><strong><span style="font-size: 16px">证书源</span></strong></p>
<p>可以将证书节点配置为从多个源加载证书:</p>
<ul>
<li><code>Path</code> 和 <code>Password</code> 用于加载 .pfx 文件。</li>
<li><code>Path</code>、<code>KeyPath</code> 和 <code>Password</code> 用于加载 .pem/<em>.crt</em> 和 .key 文件。</li>
<li><code>Subject</code> 和 <code>Store</code> 用于从证书存储中加载。</li>
</ul>
<hr>
<p>好了,罗嗦话说完了,我们抽取文档的一部分进行实践</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> builder =<span style="color: rgba(0, 0, 0, 1)"> WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions </span>=><span style="color: rgba(0, 0, 0, 1)">
{
serverOptions.ListenAnyIP(</span><span style="color: rgba(128, 0, 128, 1)">5005</span>, listenOptions =><span style="color: rgba(0, 0, 0, 1)">
{
listenOptions.UseHttps(httpsOptions </span>=><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> localhostCert =<span style="color: rgba(0, 0, 0, 1)"> CertificateLoader.LoadFromStoreCert(
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">localhost</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">My</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, StoreLocation.CurrentUser,
allowInvalid: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> exampleCert =<span style="color: rgba(0, 0, 0, 1)"> CertificateLoader.LoadFromStoreCert(
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">example.com</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">My</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, StoreLocation.CurrentUser,
allowInvalid: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> subExampleCert =<span style="color: rgba(0, 0, 0, 1)"> CertificateLoader.LoadFromStoreCert(
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sub.example.com</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">My</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, StoreLocation.CurrentUser,
allowInvalid: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> certs = <span style="color: rgba(0, 0, 255, 1)">new</span> Dictionary<<span style="color: rgba(0, 0, 255, 1)">string</span>, X509Certificate2><span style="color: rgba(0, 0, 0, 1)">(
StringComparer.OrdinalIgnoreCase)
{
[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">localhost</span><span style="color: rgba(128, 0, 0, 1)">"</span>] =<span style="color: rgba(0, 0, 0, 1)"> localhostCert,
[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">example.com</span><span style="color: rgba(128, 0, 0, 1)">"</span>] =<span style="color: rgba(0, 0, 0, 1)"> exampleCert,
[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sub.example.com</span><span style="color: rgba(128, 0, 0, 1)">"</span>] =<span style="color: rgba(0, 0, 0, 1)"> subExampleCert
};
httpsOptions.ServerCertificateSelector </span>= (connectionContext, name) =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (name <span style="color: rgba(0, 0, 255, 1)">is</span> not <span style="color: rgba(0, 0, 255, 1)">null</span> && certs.TryGetValue(name, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> cert))
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> cert;
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> exampleCert;
};
});
});
});</span></pre>
</div>
<p> 上面的代码一看就能懂,比较无奈的是官方文档的 SSL 证书是从 证书存储区 里获取的,在实际应用中,明显是不够方便,最好是那种直接写 证书路径 和 密码的,这样才能一目了然嘛,而这里的关键就是 X509Certificate2 这个类了,可以看到,最终是通过检索一个字典返回的,接受的就是这个 X509Certificate2 类,所以我们看看这个类到底是个什么东西,传送门;</p>
<p> 这里我们只关注构造函数,下面是官方文档,或者直接 F12 进去看更为直接</p>
<hr>
<p> </p>
<p id="constructors" class="heading-anchor"><strong><span style="font-size: 18px">构造函数</span></strong></p>
<table class="nameValue table table-sm table-stacked-mobile">
<tbody>
<tr>
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2()</span></td>
<td class="summary">
<div data-moniker=" net-6.0 net-7.0 ">已过时。</div>
<p>初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr>
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(Byte[])</span></td>
<td class="summary">
<p>使用来自字节数组的信息初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr data-moniker=" net-5.0 net-6.0 net-7.0 netcore-2.0 netcore-2.1 netcore-2.2 netcore-3.0 netcore-3.1 netframework-2.0 netframework-3.0 netframework-3.5 netframework-4.0 netframework-4.5 netframework-4.5.1 netframework-4.5.2 netframework-4.6 netframework-4.6.1 netframework-4.6.2 netframework-4.7 netframework-4.7.1 netframework-4.7.2 netframework-4.8 netstandard-2.0 netstandard-2.1 xamarinandroid-7.1 xamarinios-10.8 xamarinmac-3.0 ">
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(Byte[], SecureString)</span></td>
<td class="summary">
<p>使用一个字节数组和一个密码初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr data-moniker=" net-5.0 net-6.0 net-7.0 netcore-2.0 netcore-2.1 netcore-2.2 netcore-3.0 netcore-3.1 netframework-2.0 netframework-3.0 netframework-3.5 netframework-4.0 netframework-4.5 netframework-4.5.1 netframework-4.5.2 netframework-4.6 netframework-4.6.1 netframework-4.6.2 netframework-4.7 netframework-4.7.1 netframework-4.7.2 netframework-4.8 netstandard-2.0 netstandard-2.1 xamarinandroid-7.1 xamarinios-10.8 xamarinmac-3.0 ">
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(Byte[], SecureString, X509KeyStorageFlags)</span></td>
<td class="summary">
<p>使用一个字节数组、一个密码和一个密钥存储标志初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr>
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(Byte[], String)</span></td>
<td class="summary">
<p>使用一个字节数组和一个密码初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr>
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(Byte[], String, X509KeyStorageFlags)</span></td>
<td class="summary">
<p>使用一个字节数组、一个密码和一个密钥存储标志初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr>
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(IntPtr)</span></td>
<td class="summary">
<p>使用非托管句柄初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr data-moniker=" net-5.0 net-6.0 net-7.0 ">
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(ReadOnlySpan<Byte>)</span></td>
<td class="summary">
<p>用证书数据初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr data-moniker=" net-5.0 net-6.0 net-7.0 ">
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(ReadOnlySpan<Byte>, ReadOnlySpan<Char>, X509KeyStorageFlags)</span></td>
<td class="summary">
<p>使用证书数据、密码和密钥存储标志初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr data-moniker=" net-5.0 net-6.0 net-7.0 netcore-2.0 netcore-2.1 netcore-2.2 netcore-3.0 netcore-3.1 netframework-4.0 netframework-4.5 netframework-4.5.1 netframework-4.5.2 netframework-4.6 netframework-4.6.1 netframework-4.6.2 netframework-4.7 netframework-4.7.1 netframework-4.7.2 netframework-4.8 netstandard-2.0 netstandard-2.1 xamarinandroid-7.1 xamarinios-10.8 xamarinmac-3.0 ">
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(SerializationInfo, StreamingContext)</span></td>
<td class="summary">
<p>使用指定的序列化和流上下文信息初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr>
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(String)</span></td>
<td class="summary">
<p>使用证书文件名初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr data-moniker=" net-5.0 net-6.0 net-7.0 ">
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(String, ReadOnlySpan<Char>, X509KeyStorageFlags)</span></td>
<td class="summary">
<p>使用一个证书文件名、一个密码和一个密钥存储标志初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr data-moniker=" net-5.0 net-6.0 net-7.0 netcore-2.0 netcore-2.1 netcore-2.2 netcore-3.0 netcore-3.1 netframework-2.0 netframework-3.0 netframework-3.5 netframework-4.0 netframework-4.5 netframework-4.5.1 netframework-4.5.2 netframework-4.6 netframework-4.6.1 netframework-4.6.2 netframework-4.7 netframework-4.7.1 netframework-4.7.2 netframework-4.8 netstandard-2.0 netstandard-2.1 xamarinandroid-7.1 xamarinios-10.8 xamarinmac-3.0 ">
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(String, SecureString)</span></td>
<td class="summary">
<p>使用一个证书文件名和一个密码初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr data-moniker=" net-5.0 net-6.0 net-7.0 netcore-2.0 netcore-2.1 netcore-2.2 netcore-3.0 netcore-3.1 netframework-2.0 netframework-3.0 netframework-3.5 netframework-4.0 netframework-4.5 netframework-4.5.1 netframework-4.5.2 netframework-4.6 netframework-4.6.1 netframework-4.6.2 netframework-4.7 netframework-4.7.1 netframework-4.7.2 netframework-4.8 netstandard-2.0 netstandard-2.1 xamarinandroid-7.1 xamarinios-10.8 xamarinmac-3.0 ">
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(String, SecureString, X509KeyStorageFlags)</span></td>
<td class="summary">
<p>使用一个证书文件名、一个密码和一个密钥存储标志初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr>
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(String, String)</span></td>
<td class="summary">
<p>使用一个证书文件名和一个用于访问该证书的密码初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr>
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(String, String, X509KeyStorageFlags)</span></td>
<td class="summary">
<p>使用一个证书文件名、一个用于访问该证书的密码和一个密钥存储标志初始化 X509Certificate2 类的新实例。</p>
</td>
</tr>
<tr data-moniker=" net-5.0 net-6.0 net-7.0 netcore-2.0 netcore-2.1 netcore-2.2 netcore-3.0 netcore-3.1 netframework-2.0 netframework-3.0 netframework-3.5 netframework-4.0 netframework-4.5 netframework-4.5.1 netframework-4.5.2 netframework-4.6 netframework-4.6.1 netframework-4.6.2 netframework-4.7 netframework-4.7.1 netframework-4.7.2 netframework-4.8 netstandard-2.0 netstandard-2.1 xamarinandroid-7.1 xamarinios-10.8 xamarinmac-3.0 ">
<td class="is-one-third-width-tablet"><span class="break-text">X509Certificate2(X509Certificate)</span></td>
<td class="summary">
<p>使用 X509Certificate 对象初始化 X509Certificate2 类的新实例。</p>
<div> </div>
</td>
</tr>
</tbody>
</table>
<hr>
<p> 直接 F12 也贴出来吧,方便大伙查看</p>
<p><img src="https://img2022.cnblogs.com/blog/1897432/202209/1897432-20220922235127616-1962596990.png" alt="" loading="lazy" style="display: block; margin-left: auto; margin-right: auto"></p>
<p> </p>
<p> 帅气的小伙伴可能已经发现了,里面就存在了一个 直接传入 文件路径 和 密码 作为参数的构造函数,毫无疑问,它就是我们要找的!!!下面我们直接看代码:</p>
<p>Program.cs</p>
<div class="cnblogs_code">
<pre>builder.WebHost.ConfigureKestrel(serverOptions =><span style="color: rgba(0, 0, 0, 1)">
{
serverOptions.ConfigureHttpsDefaults(listenOptions </span>=><span style="color: rgba(0, 0, 0, 1)">
{
listenOptions.SslProtocols </span>=<span style="color: rgba(0, 0, 0, 1)"> SslProtocols.Tls13;
});
serverOptions.ListenAnyIP(</span><span style="color: rgba(128, 0, 128, 1)">5209</span>, listenOptions =><span style="color: rgba(0, 0, 0, 1)">
{
listenOptions.UseHttps(httpsOptions </span>=><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> test1 = <span style="color: rgba(0, 0, 255, 1)">new</span> X509Certificate2(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">cer\\test1.ysmc.net.cn_server.pfx</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">密码1</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> test2 = <span style="color: rgba(0, 0, 255, 1)">new</span> X509Certificate2(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">cer\\test2.ysmc.net.cn_server.pfx</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">密码2</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> certs = <span style="color: rgba(0, 0, 255, 1)">new</span> Dictionary<<span style="color: rgba(0, 0, 255, 1)">string</span>, X509Certificate2><span style="color: rgba(0, 0, 0, 1)">(
StringComparer.OrdinalIgnoreCase)
{
[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">test1.ysmc.net.cn</span><span style="color: rgba(128, 0, 0, 1)">"</span>] =<span style="color: rgba(0, 0, 0, 1)"> test1,
[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">test2.ysmc.net.cn</span><span style="color: rgba(128, 0, 0, 1)">"</span>] =<span style="color: rgba(0, 0, 0, 1)"> test2
};
httpsOptions.ServerCertificateSelector </span>= (connectionContext, name) =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (name <span style="color: rgba(0, 0, 255, 1)">is</span> not <span style="color: rgba(0, 0, 255, 1)">null</span> && certs.TryGetValue(name, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> cert))
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> cert;
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> test1;
};
});
});
});</span></pre>
</div>
<p><img src="https://img2022.cnblogs.com/blog/1897432/202209/1897432-20220922235603097-643993874.png" alt="" loading="lazy" style="display: block; margin-left: auto; margin-right: auto"></p>
<p> </p>
<p> 因为是配合了 YARP “食用”的,详情可以查看我前面的文章,传送门,所以不同的域名会反向代理到不同的网站上面,好了,文章到此结束,感谢大佬们的阅读,谢谢!</p>
<p>原文链接:https://www.cnblogs.com/ysmc/p/16721268.html</p>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自博客园,作者:一事冇诚,转载请注明原文链接:https://www.cnblogs.com/ysmc/p/16721268.html</p><br><br>
来源:https://www.cnblogs.com/ysmc/p/16721268.html
頁:
[1]