2026-02-06 03:34:50 +08:00
|
|
|
|
<!--
|
|
|
|
|
|
SslTerminationDemo.vue
|
|
|
|
|
|
SSL终结 - HTTPS卸载/证书管理
|
|
|
|
|
|
-->
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="ssl-termination-demo">
|
|
|
|
|
|
<div class="header">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="title">
|
|
|
|
|
|
🔒 SSL 终结:HTTPS 流量的"解密官"
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="subtitle">
|
|
|
|
|
|
想象成公司的前台接待——对外使用正式头衔(HTTPS),对内用内部称呼(HTTP),负责"翻译"身份
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="ssl-flow">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="flow-title">
|
|
|
|
|
|
🔐 HTTPS 流量解密流程
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="flow-diagram">
|
|
|
|
|
|
<!-- 客户端 -->
|
|
|
|
|
|
<div class="flow-node client">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="node-icon">
|
|
|
|
|
|
👤
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="node-label">
|
|
|
|
|
|
客户端 (浏览器)
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="node-detail">
|
|
|
|
|
|
发起 HTTPS 请求
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="flow-arrow encrypted">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="arrow-line" />
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<div class="arrow-label">
|
|
|
|
|
|
<span class="lock-icon">🔒</span>
|
|
|
|
|
|
<span>TLS 加密连接</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cert-info">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="cert-item">
|
|
|
|
|
|
<span class="cert-label">证书:</span> *.example.com
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cert-item">
|
|
|
|
|
|
<span class="cert-label">算法:</span> TLS 1.3
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cert-item">
|
|
|
|
|
|
<span class="cert-label">加密:</span> AES-256-GCM
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Nginx -->
|
|
|
|
|
|
<div class="flow-node nginx">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="node-icon">
|
|
|
|
|
|
🚪
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="node-label">
|
|
|
|
|
|
Nginx (SSL 终结)
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<div class="node-actions">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="action">
|
|
|
|
|
|
<span class="action-icon">📜</span> 校验证书
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="action">
|
|
|
|
|
|
<span class="action-icon">🔓</span> 解密流量
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="action">
|
|
|
|
|
|
<span class="action-icon">📝</span> 添加 X-Forwarded-*
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="flow-arrow plain">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="arrow-line" />
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<div class="arrow-label">
|
|
|
|
|
|
<span class="unlock-icon">🔓</span>
|
|
|
|
|
|
<span>HTTP 明文</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="headers-info">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="header-item">
|
|
|
|
|
|
X-Forwarded-For: 203.0.113.42
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="header-item">
|
|
|
|
|
|
X-Forwarded-Proto: https
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="header-item">
|
|
|
|
|
|
X-Real-IP: 203.0.113.42
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 后端服务 -->
|
|
|
|
|
|
<div class="flow-node backend">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="node-icon">
|
|
|
|
|
|
⚙️
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="node-label">
|
|
|
|
|
|
后端服务集群
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="node-detail">
|
|
|
|
|
|
专注于业务逻辑,无需处理 TLS
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="cert-management">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="section-title">
|
|
|
|
|
|
📜 SSL 证书管理
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="cert-tabs">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="tab in certTabs"
|
|
|
|
|
|
:key="tab.id"
|
|
|
|
|
|
:class="['cert-tab', { active: currentCertTab === tab.id }]"
|
|
|
|
|
|
@click="currentCertTab = tab.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ tab.name }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="cert-content">
|
|
|
|
|
|
<!-- 证书申请流程 -->
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="currentCertTab === 'apply'"
|
|
|
|
|
|
class="apply-flow"
|
|
|
|
|
|
>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<div class="flow-steps">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-for="(step, index) in certSteps"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
class="cert-step"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="step-badge">
|
|
|
|
|
|
{{ index + 1 }}
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<div class="step-content">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="step-title">
|
|
|
|
|
|
{{ step.title }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="step-desc">
|
|
|
|
|
|
{{ step.desc }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="step.command"
|
|
|
|
|
|
class="step-command"
|
|
|
|
|
|
>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<code>{{ step.command }}</code>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Nginx 配置 -->
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="currentCertTab === 'config'"
|
|
|
|
|
|
class="nginx-config"
|
|
|
|
|
|
>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<pre class="config-block"><code>server {
|
|
|
|
|
|
listen 443 ssl http2;
|
|
|
|
|
|
server_name api.example.com;
|
|
|
|
|
|
|
|
|
|
|
|
# SSL 证书配置
|
|
|
|
|
|
ssl_certificate /etc/nginx/ssl/api.example.com.crt;
|
|
|
|
|
|
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
|
|
|
|
|
|
|
|
|
|
|
|
# SSL 协议和密码套件
|
|
|
|
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
|
|
|
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
|
|
|
|
|
|
ssl_prefer_server_ciphers off;
|
|
|
|
|
|
|
|
|
|
|
|
# SSL 会话缓存
|
|
|
|
|
|
ssl_session_cache shared:SSL:50m;
|
|
|
|
|
|
ssl_session_timeout 1d;
|
|
|
|
|
|
ssl_session_tickets off;
|
|
|
|
|
|
|
|
|
|
|
|
# OCSP Stapling
|
|
|
|
|
|
ssl_stapling on;
|
|
|
|
|
|
ssl_stapling_verify on;
|
|
|
|
|
|
ssl_trusted_certificate /etc/nginx/ssl/chain.crt;
|
|
|
|
|
|
resolver 8.8.8.8 8.8.4.4 valid=300s;
|
|
|
|
|
|
resolver_timeout 5s;
|
|
|
|
|
|
|
|
|
|
|
|
# 安全响应头
|
|
|
|
|
|
add_header Strict-Transport-Security "max-age=63072000" always;
|
|
|
|
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
|
|
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
|
|
|
|
|
|
|
|
|
|
location / {
|
|
|
|
|
|
proxy_pass http://backend;
|
|
|
|
|
|
proxy_set_header Host $host;
|
|
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# HTTP 重定向到 HTTPS
|
|
|
|
|
|
server {
|
|
|
|
|
|
listen 80;
|
|
|
|
|
|
server_name api.example.com;
|
|
|
|
|
|
return 301 https://$server_name$request_uri;
|
|
|
|
|
|
}</code></pre>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 最佳实践 -->
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="currentCertTab === 'bestpractice'"
|
|
|
|
|
|
class="best-practices"
|
|
|
|
|
|
>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<div class="practices-grid">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-for="practice in bestPractices"
|
|
|
|
|
|
:key="practice.id"
|
|
|
|
|
|
class="practice-card"
|
|
|
|
|
|
>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<div class="practice-header">
|
|
|
|
|
|
<span class="practice-icon">{{ practice.icon }}</span>
|
|
|
|
|
|
<span class="practice-title">{{ practice.title }}</span>
|
|
|
|
|
|
</div>
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="practice-content">
|
|
|
|
|
|
{{ practice.content }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="practice.code"
|
|
|
|
|
|
class="practice-code"
|
|
|
|
|
|
>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
<code>{{ practice.code }}</code>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="benefits-section">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="section-title">
|
|
|
|
|
|
✨ SSL 终结的核心优势
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="benefits-grid">
|
|
|
|
|
|
<div class="benefit-card">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="benefit-icon">
|
|
|
|
|
|
🚀
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="benefit-title">
|
|
|
|
|
|
性能提升
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="benefit-desc">
|
|
|
|
|
|
TLS 握手和加密解密是 CPU 密集型操作,集中在 Nginx 处理,后端服务专注业务逻辑,整体吞吐量提升 2-5 倍
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="benefit-card">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="benefit-icon">
|
|
|
|
|
|
🔧
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="benefit-title">
|
|
|
|
|
|
简化运维
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="benefit-desc">
|
|
|
|
|
|
证书统一管理,只需在 Nginx 配置一次,无需在每个后端服务重复配置,证书续期、更换一键完成
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="benefit-card">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="benefit-icon">
|
|
|
|
|
|
🛡️
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="benefit-title">
|
|
|
|
|
|
集中安全
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="benefit-desc">
|
|
|
|
|
|
SSL/TLS 配置统一管控,强制使用最新协议版本和密码套件,统一添加安全响应头(HSTS、CSP 等)
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="benefit-card">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="benefit-icon">
|
|
|
|
|
|
📊
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="benefit-title">
|
|
|
|
|
|
统一监控
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="benefit-desc">
|
|
|
|
|
|
所有 HTTPS 流量经过 Nginx,可以统一记录访问日志、分析 SSL 握手性能、监控证书有效期,便于审计和排障
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, computed, watch } from 'vue'
|
|
|
|
|
|
|
|
|
|
|
|
// 证书管理标签
|
|
|
|
|
|
const certTabs = [
|
|
|
|
|
|
{ id: 'apply', name: '证书申请' },
|
|
|
|
|
|
{ id: 'config', name: 'Nginx 配置' },
|
|
|
|
|
|
{ id: 'bestpractice', name: '最佳实践' }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
const currentCertTab = ref('apply')
|
|
|
|
|
|
|
|
|
|
|
|
// 证书申请步骤
|
|
|
|
|
|
const certSteps = [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '生成私钥',
|
|
|
|
|
|
desc: '使用 OpenSSL 生成 RSA 私钥,这是证书的基础',
|
|
|
|
|
|
command: 'openssl genrsa -out private.key 2048'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '创建 CSR',
|
|
|
|
|
|
desc: '生成证书签名请求,包含域名和组织信息',
|
|
|
|
|
|
command: 'openssl req -new -key private.key -out csr.pem'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '域名验证',
|
|
|
|
|
|
desc: 'CA 机构验证域名所有权(DNS 记录或 HTTP 文件)',
|
|
|
|
|
|
command: '# 添加 DNS TXT 记录 或 上传验证文件到 /.well-known/'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '签发证书',
|
|
|
|
|
|
desc: '验证通过后,CA 签发证书文件',
|
|
|
|
|
|
command: '# 下载 certificate.crt 和 chain.crt'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '部署配置',
|
|
|
|
|
|
desc: '将证书配置到 Nginx 并测试',
|
|
|
|
|
|
command: 'nginx -t && systemctl reload nginx'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// 最佳实践
|
|
|
|
|
|
const bestPractices = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'protocol',
|
|
|
|
|
|
icon: '🔐',
|
|
|
|
|
|
title: '使用 TLS 1.2+',
|
|
|
|
|
|
content: '禁用 SSLv3、TLS 1.0/1.1 等老旧协议,仅启用 TLS 1.2 和 1.3',
|
|
|
|
|
|
code: 'ssl_protocols TLSv1.2 TLSv1.3;'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'cipher',
|
|
|
|
|
|
icon: '🛡️',
|
|
|
|
|
|
title: '强密码套件',
|
|
|
|
|
|
content: '禁用弱加密算法,优先使用 ECDHE 和 AES-GCM',
|
|
|
|
|
|
code: 'ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'hsts',
|
|
|
|
|
|
icon: '🔒',
|
|
|
|
|
|
title: 'HSTS 头部',
|
|
|
|
|
|
content: '强制浏览器始终使用 HTTPS 访问,防止 SSL 剥离攻击',
|
|
|
|
|
|
code: 'add_header Strict-Transport-Security "max-age=63072000" always;'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'ocsp',
|
|
|
|
|
|
icon: '✅',
|
|
|
|
|
|
title: 'OCSP Stapling',
|
|
|
|
|
|
content: '启用 OCSP 装订,加速 SSL 握手并保护用户隐私',
|
|
|
|
|
|
code: 'ssl_stapling on; ssl_stapling_verify on;'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.ssl-termination-demo {
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
background: var(--vp-c-bg-soft);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 1.5rem;
|
|
|
|
|
|
margin: 1.5rem 0;
|
|
|
|
|
|
font-family: var(--vp-font-family-base);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
|
margin-bottom: 1.5rem;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.title {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: 1.2rem;
|
|
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.subtitle {
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ssl-flow {
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 1.5rem;
|
|
|
|
|
|
margin-bottom: 1.5rem;
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-title {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: 1.1rem;
|
|
|
|
|
|
margin-bottom: 1.5rem;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-diagram {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-node {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.5rem;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
padding: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
background: var(--vp-c-bg-soft);
|
|
|
|
|
|
border: 2px solid var(--vp-c-divider);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-node.client {
|
|
|
|
|
|
border-color: #3b82f6;
|
|
|
|
|
|
background: linear-gradient(135deg, #dbeafe, #bfdbfe);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-node.nginx {
|
|
|
|
|
|
border-color: #22c55e;
|
|
|
|
|
|
background: linear-gradient(135deg, #dcfce7, #bbf7d0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-node.backend {
|
|
|
|
|
|
border-color: #8b5cf6;
|
|
|
|
|
|
background: linear-gradient(135deg, #f3e8ff, #e9d5ff);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.node-icon {
|
|
|
|
|
|
font-size: 2rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.node-label {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.node-detail {
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.node-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 0.25rem;
|
|
|
|
|
|
margin-top: 0.5rem;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.25rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-icon {
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-arrow {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
padding: 0.5rem 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.arrow-line {
|
|
|
|
|
|
height: 2px;
|
|
|
|
|
|
background: var(--vp-c-divider);
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.arrow-line::after {
|
|
|
|
|
|
content: '▼';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
bottom: -8px;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
|
color: var(--vp-c-divider);
|
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-arrow.encrypted .arrow-line {
|
|
|
|
|
|
background: linear-gradient(90deg, #22c55e, #3b82f6);
|
|
|
|
|
|
height: 3px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-arrow.encrypted .arrow-line::after {
|
|
|
|
|
|
color: #22c55e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.arrow-label {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: -12px;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
padding: 0 0.5rem;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.25rem;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.lock-icon {
|
|
|
|
|
|
color: #22c55e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.unlock-icon {
|
|
|
|
|
|
color: #f59e0b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-info,
|
|
|
|
|
|
.headers-info {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 15px;
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
|
font-size: 0.7rem;
|
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-info {
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.headers-info {
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-item,
|
|
|
|
|
|
.header-item {
|
|
|
|
|
|
margin: 0.15rem 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-label {
|
|
|
|
|
|
color: var(--vp-c-brand);
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-management {
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 1.5rem;
|
|
|
|
|
|
margin-bottom: 1.5rem;
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: 1.1rem;
|
|
|
|
|
|
margin-bottom: 1rem;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-tabs {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
margin-bottom: 1.5rem;
|
|
|
|
|
|
border-bottom: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
padding-bottom: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-tab {
|
|
|
|
|
|
padding: 0.5rem 1rem;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-tab:hover {
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
background: var(--vp-c-bg-soft);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-tab.active {
|
|
|
|
|
|
color: var(--vp-c-brand);
|
|
|
|
|
|
background: rgba(var(--vp-c-brand-rgb), 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-content {
|
|
|
|
|
|
min-height: 200px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.apply-flow {
|
|
|
|
|
|
padding: 1rem 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-steps {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-step {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
background: var(--vp-c-bg-soft);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
padding: 0.75rem;
|
|
|
|
|
|
border-radius: 6px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
border-left: 3px solid var(--vp-c-brand);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-badge {
|
|
|
|
|
|
width: 28px;
|
|
|
|
|
|
height: 28px;
|
|
|
|
|
|
background: var(--vp-c-brand);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-title {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
|
margin-bottom: 0.25rem;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-desc {
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-command {
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nginx-config {
|
|
|
|
|
|
background: var(--vp-c-bg-alt);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
padding: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.config-block {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
white-space: pre;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.best-practices {
|
|
|
|
|
|
padding: 1rem 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.practices-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.practice-card {
|
|
|
|
|
|
background: var(--vp-c-bg-soft);
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
padding: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.practice-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.practice-icon {
|
|
|
|
|
|
font-size: 1.25rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.practice-title {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.practice-content {
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.practice-code {
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.benefits-section {
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 1.5rem;
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.benefits-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
margin-top: 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.benefit-card {
|
|
|
|
|
|
background: var(--vp-c-bg-soft);
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
padding: 1.25rem;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.benefit-card:hover {
|
|
|
|
|
|
transform: translateY(-3px);
|
|
|
|
|
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.benefit-icon {
|
|
|
|
|
|
font-size: 2.5rem;
|
|
|
|
|
|
margin-bottom: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.benefit-title {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
|
|
color: var(--vp-c-text-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.benefit-desc {
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.strategy-tabs {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-tabs {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 0.25rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pool-header {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.servers-grid {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow-node {
|
|
|
|
|
|
padding: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cert-info,
|
|
|
|
|
|
.headers-info {
|
|
|
|
|
|
position: static;
|
|
|
|
|
|
margin-top: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|