# Apache 虚拟主机安全整改方案

生成时间：2026-06-20  
目标文件：`C:\xampp\apache\conf\extra\httpd-vhosts.conf`  
关联审查报告：`C:\xampp\htdocs\htdocs_security_review_20260620.md`

## 一、整改目标

当前 `C:\xampp\htdocs` 下同时存在前端静态文件、后端源码、`.env`、数据库备份、日志、APK、JKS、Excel 数据、`node_modules`、旧 PHP 系统等内容。Apache 虚拟主机配置如果继续暴露整个 `htdocs`，会放大敏感文件泄露风险。

本方案目标是：

1. 公网只暴露必要服务：
   - 管理后台：`https://buyrdbcdgdst.cv`
   - 控制端：`https://yybfok.store`
   - WebSocket：`wss://sunwukongzhubajjie.lat/api/ws/` 和 `wss://sunwukongzhubajjie.lat/ws`
2. HTTP 统一跳转 HTTPS。
3. 禁止 Apache 访问 `.env`、`.sql`、`.log`、`.xlsx`、`.jks`、`private`、`backend`、`src`、`node_modules`、`vendor` 等敏感文件和目录。
4. 禁止目录索引。
5. 限制 `/downloads` 只允许 APK 文件下载。
6. `localhost` 不再暴露整个 `C:\xampp\htdocs`。
7. 保持现有业务代理关系可用。

---

## 二、当前虚拟主机配置中的主要风险

### 1. `localhost` 暴露整个 `htdocs`

当前配置中存在：

```apache
<VirtualHost *:80>
  ServerName localhost
  DocumentRoot "C:/xampp/htdocs"

  <Directory "C:/xampp/htdocs">
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
  </Directory>
</VirtualHost>
```

风险：

- 允许访问整个 `C:/xampp/htdocs`。
- 开启 `Indexes` 后，部分目录可能被列目录。
- `AllowOverride All` 允许目录内 `.htaccess` 改变访问规则。
- 目录中存在 `.env`、备份、日志、JKS、源码等敏感内容。

这是最高优先级需要整改的问题。

---

### 2. HTTP 公网域名没有统一跳转 HTTPS

当前公网 HTTPS 域名包括：

- `buyrdbcdgdst.cv`
- `yybfok.store`
- `sunwukongzhubajjie.lat`

但没有看到对应的 `*:80` 公网域名跳转配置。访问 HTTP 时可能落入默认虚拟主机，导致访问路径不可控。

---

### 3. 部分目录仍启用 `Options Indexes`

例如：

```apache
Options Indexes FollowSymLinks
```

建议统一改成：

```apache
Options -Indexes -Includes -ExecCGI +FollowSymLinks
```

---

### 4. `/downloads` 目录直接公开

当前控制端有：

```apache
Alias /downloads "C:/xampp/htdocs/shayukeji/shark-control/downloads"
Require all granted
```

如果该目录中混入非 APK 文件，可能被直接下载。

建议 Apache 层只允许 `.apk`，其他文件全部拒绝。

---

### 5. 后端源码、依赖、运行配置位于 Web 根下

需要 Apache 层封禁：

- `.env`
- `*.sql`
- `*.log`
- `*.xlsx`
- `*.jks`
- `private/`
- `backend/`
- `src/`
- `node_modules/`
- `vendor/`
- `db-backups*/`
- `keystores/`

更理想做法是后续把这些文件迁出 `htdocs`。

---

## 三、推荐修改步骤

### 第 1 步：备份原配置

在 PowerShell 中执行：

```powershell
Copy-Item "C:\xampp\apache\conf\extra\httpd-vhosts.conf" "C:\xampp\apache\conf\extra\httpd-vhosts.conf.bak-20260620"
```

---

### 第 2 步：删除或注释过时指令

当前有：

```apache
NameVirtualHost *:80
```

Apache 2.4 已不需要，会产生警告。建议改成：

```apache
# NameVirtualHost *:80
```

---

### 第 3 步：确认 Apache 模块已启用

检查 `C:\xampp\apache\conf\httpd.conf`，确认以下模块没有被注释：

```apache
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule headers_module modules/mod_headers.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
```

其中：

- `mod_rewrite`：SPA 路由和 HTTP 跳 HTTPS 需要。
- `mod_ssl`：HTTPS 需要。
- `mod_headers`：安全响应头需要。
- `mod_proxy`、`mod_proxy_http`：HTTP API 反代需要。
- `mod_proxy_wstunnel`：WebSocket 反代需要。

---

## 四、推荐版 `httpd-vhosts.conf`

> 使用前请先备份原文件。以下配置按当前业务域名和端口整理。

```apache
# Virtual Hosts
# Required modules:
# mod_log_config, mod_proxy, mod_proxy_http, mod_proxy_wstunnel,
# mod_rewrite, mod_ssl, mod_headers

# Apache 2.4 不再需要 NameVirtualHost
# NameVirtualHost *:80

# ============================================================
# Global hardening for htdocs
# ============================================================

ServerTokens Prod
ServerSignature Off

# 默认禁止访问整个 htdocs，后面只对白名单目录单独放行
<Directory "C:/xampp/htdocs">
  Options -Indexes -Includes -ExecCGI +FollowSymLinks
  AllowOverride None
  Require all denied
</Directory>

# 禁止访问常见敏感文件
<FilesMatch "(?i)^(\.env|\.env\..*|.*\.sql|.*\.log|.*\.bak|.*\.backup|.*\.old|.*\.zip|.*\.rar|.*\.7z|.*\.jks|.*\.pem|.*\.key|composer\.(json|lock)|package(-lock)?\.json|tsconfig.*\.json|vite\.config\..*)$">
  Require all denied
</FilesMatch>

# 禁止访问敏感目录名
<LocationMatch "(?i)/(private|node_modules|vendor|src|backend|prisma|logs?|db-backups[^/]*|keystores|\.git|\.svn|\.idea|\.vscode)(/|$)">
  Require all denied
</LocationMatch>

# ============================================================
# Public HTTP -> HTTPS redirect
# ============================================================

<VirtualHost *:80>
  ServerName buyrdbcdgdst.cv
  RewriteEngine On
  RewriteRule ^ https://buyrdbcdgdst.cv%{REQUEST_URI} [R=301,L]

  ErrorLog "logs/buyrdbcdgdst-admin-http-error.log"
  CustomLog "logs/buyrdbcdgdst-admin-http-access.log" common
</VirtualHost>

<VirtualHost *:80>
  ServerName yybfok.store
  RewriteEngine On
  RewriteRule ^ https://yybfok.store%{REQUEST_URI} [R=301,L]

  ErrorLog "logs/yybfok-control-http-error.log"
  CustomLog "logs/yybfok-control-http-access.log" common
</VirtualHost>

<VirtualHost *:80>
  ServerName sunwukongzhubajjie.lat
  RewriteEngine On
  RewriteRule ^ https://sunwukongzhubajjie.lat%{REQUEST_URI} [R=301,L]

  ErrorLog "logs/ws-http-error.log"
  CustomLog "logs/ws-http-access.log" common
</VirtualHost>

# ============================================================
# 管理后台本地域名：shark-admin.local
# 仅建议本机调试使用
# ============================================================

<VirtualHost *:80>
  ServerName shark-admin.local
  DocumentRoot "C:/xampp/htdocs/shayukeji/shark-control-admin/frontend/dist"

  <Directory "C:/xampp/htdocs/shayukeji/shark-control-admin/frontend/dist">
    Options -Indexes -Includes -ExecCGI +FollowSymLinks
    AllowOverride None
    Require local

    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.html [L]
  </Directory>

  <IfModule proxy_module>
    ProxyRequests Off
    ProxyPreserveHost On

    ProxyPass        /api/     http://127.0.0.1:3001/api/
    ProxyPassReverse /api/     http://127.0.0.1:3001/api/

    ProxyPass        /uploads/ http://127.0.0.1:3001/uploads/
    ProxyPassReverse /uploads/ http://127.0.0.1:3001/uploads/
  </IfModule>

  ErrorLog "logs/shark-admin-error.log"
  CustomLog "logs/shark-admin-access.log" common
</VirtualHost>

# ============================================================
# 管理后台 HTTPS 公网域名：buyrdbcdgdst.cv
# ============================================================

<VirtualHost *:443>
  ServerName buyrdbcdgdst.cv
  DocumentRoot "C:/xampp/htdocs/shayukeji/shark-control-admin/frontend/dist"

  SSLEngine on
  SSLCertificateFile    "C:/xampp/apache/conf/ssl/buyrdbcdgdst.cv-chain.pem"
  SSLCertificateKeyFile "C:/xampp/apache/conf/ssl/buyrdbcdgdst.cv-key.pem"

  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  Header always set X-Content-Type-Options "nosniff"
  Header always set X-Frame-Options "DENY"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"

  <Directory "C:/xampp/htdocs/shayukeji/shark-control-admin/frontend/dist">
    Options -Indexes -Includes -ExecCGI +FollowSymLinks
    AllowOverride None
    Require all granted

    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.html [L]
  </Directory>

  # 禁止敏感 URL
  <LocationMatch "(?i)/(\.env|package\.json|package-lock\.json|src|backend|node_modules|prisma|logs?|uploads/.*\.(php|phtml|phar|cgi|pl|exe|bat|cmd|ps1|js|html|svg))">
    Require all denied
  </LocationMatch>

  <IfModule proxy_module>
    ProxyRequests Off
    ProxyPreserveHost On

    ProxyPass        /api/     http://127.0.0.1:3001/api/
    ProxyPassReverse /api/     http://127.0.0.1:3001/api/

    ProxyPass        /uploads/ http://127.0.0.1:3001/uploads/
    ProxyPassReverse /uploads/ http://127.0.0.1:3001/uploads/
  </IfModule>

  ErrorLog "logs/buyrdbcdgdst-admin-ssl-error.log"
  CustomLog "logs/buyrdbcdgdst-admin-ssl-access.log" common
</VirtualHost>

# ============================================================
# 控制端本地域名：shark-control.local
# 仅建议本机调试使用
# ============================================================

<VirtualHost *:80>
  ServerName shark-control.local
  DocumentRoot "C:/xampp/htdocs/shayukeji/shark-control/dist"

  <Directory "C:/xampp/htdocs/shayukeji/shark-control/dist">
    Options -Indexes -Includes -ExecCGI +FollowSymLinks
    AllowOverride None
    Require local

    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} !^/downloads/
    RewriteRule . /index.html [L]
  </Directory>

  Alias /downloads "C:/xampp/htdocs/shayukeji/shark-control/downloads"
  <Directory "C:/xampp/htdocs/shayukeji/shark-control/downloads">
    Options -Indexes -Includes -ExecCGI +FollowSymLinks
    AllowOverride None
    Require local

    <FilesMatch ".*">
      Require all denied
    </FilesMatch>

    <FilesMatch "\.apk$">
      Require local
      ForceType application/vnd.android.package-archive
      Header set Content-Disposition "attachment"
      Header set X-Content-Type-Options "nosniff"
    </FilesMatch>
  </Directory>

  <IfModule proxy_module>
    ProxyRequests Off
    ProxyPreserveHost On

    ProxyPass /downloads !

    ProxyPass        /api/auth/       http://127.0.0.1:3001/api/auth/
    ProxyPassReverse /api/auth/       http://127.0.0.1:3001/api/auth/

    ProxyPass        /api/client/     http://127.0.0.1:3001/api/client/
    ProxyPassReverse /api/client/     http://127.0.0.1:3001/api/client/

    ProxyPass        /api/public/     http://127.0.0.1:3001/api/public/
    ProxyPassReverse /api/public/     http://127.0.0.1:3001/api/public/

    ProxyPass        /api/distribute/ http://127.0.0.1:3001/api/distribute/
    ProxyPassReverse /api/distribute/ http://127.0.0.1:3001/api/distribute/

    ProxyPass        /api/            http://127.0.0.1:3002/api/
    ProxyPassReverse /api/            http://127.0.0.1:3002/api/

    ProxyPass        /uploads/        http://127.0.0.1:3002/uploads/
    ProxyPassReverse /uploads/        http://127.0.0.1:3002/uploads/
  </IfModule>

  ErrorLog "logs/shark-control-error.log"
  CustomLog "logs/shark-control-access.log" common
</VirtualHost>

# ============================================================
# 控制端 HTTPS 公网域名：yybfok.store
# ============================================================

<VirtualHost *:443>
  ServerName yybfok.store
  DocumentRoot "C:/xampp/htdocs/shayukeji/shark-control/dist"

  SSLEngine on
  SSLCertificateFile    "C:/xampp/apache/conf/ssl/yybfok.store-chain.pem"
  SSLCertificateKeyFile "C:/xampp/apache/conf/ssl/yybfok.store-key.pem"

  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  Header always set X-Content-Type-Options "nosniff"
  Header always set X-Frame-Options "DENY"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"

  <Directory "C:/xampp/htdocs/shayukeji/shark-control/dist">
    Options -Indexes -Includes -ExecCGI +FollowSymLinks
    AllowOverride None
    Require all granted

    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} !^/downloads/
    RewriteRule . /index.html [L]
  </Directory>

  # APK 下载目录
  Alias /downloads "C:/xampp/htdocs/shayukeji/shark-control/downloads"

  <Directory "C:/xampp/htdocs/shayukeji/shark-control/downloads">
    Options -Indexes -Includes -ExecCGI +FollowSymLinks
    AllowOverride None
    Require all granted

    # 默认拒绝 downloads 下所有非 APK 文件
    <FilesMatch ".*">
      Require all denied
    </FilesMatch>

    # 只允许 APK
    <FilesMatch "\.apk$">
      Require all granted
      ForceType application/vnd.android.package-archive
      Header set Content-Disposition "attachment"
      Header set X-Content-Type-Options "nosniff"
    </FilesMatch>
  </Directory>

  # 禁止敏感 URL
  <LocationMatch "(?i)/(\.env|package\.json|package-lock\.json|src|backend|node_modules|prisma|logs?|keystores|db-backups|.*\.(sql|log|jks|pem|key|bak|backup|old|zip|rar|7z))">
    Require all denied
  </LocationMatch>

  <IfModule proxy_module>
    ProxyRequests Off
    ProxyPreserveHost On

    # 不代理 /downloads，让 Apache 直接提供 APK
    ProxyPass /downloads !

    # 这些路径走管理后台后端 3001
    ProxyPass        /api/auth/       http://127.0.0.1:3001/api/auth/
    ProxyPassReverse /api/auth/       http://127.0.0.1:3001/api/auth/

    ProxyPass        /api/client/     http://127.0.0.1:3001/api/client/
    ProxyPassReverse /api/client/     http://127.0.0.1:3001/api/client/

    ProxyPass        /api/public/     http://127.0.0.1:3001/api/public/
    ProxyPassReverse /api/public/     http://127.0.0.1:3001/api/public/

    ProxyPass        /api/distribute/ http://127.0.0.1:3001/api/distribute/
    ProxyPassReverse /api/distribute/ http://127.0.0.1:3001/api/distribute/

    # 其余 /api 走控制端后端 3002
    ProxyPass        /api/            http://127.0.0.1:3002/api/
    ProxyPassReverse /api/            http://127.0.0.1:3002/api/

    # 控制端上传文件
    ProxyPass        /uploads/        http://127.0.0.1:3002/uploads/
    ProxyPassReverse /uploads/        http://127.0.0.1:3002/uploads/
  </IfModule>

  ErrorLog "logs/yybfok-control-ssl-error.log"
  CustomLog "logs/yybfok-control-ssl-access.log" common
</VirtualHost>

# ============================================================
# localhost only
# 不再暴露整个 C:/xampp/htdocs
# ============================================================

<VirtualHost *:80>
  ServerName localhost
  DocumentRoot "C:/xampp/apache/empty"

  <Directory "C:/xampp/apache/empty">
    Options None
    AllowOverride None
    Require local
  </Directory>

  <Location "/">
    Require local
  </Location>

  ErrorLog "logs/localhost-error.log"
  CustomLog "logs/localhost-access.log" common
</VirtualHost>

# ============================================================
# WebSocket HTTPS：sunwukongzhubajjie.lat
# ============================================================

<VirtualHost *:443>
  ServerName sunwukongzhubajjie.lat

  DocumentRoot "C:/xampp/apache/empty"

  SSLEngine on
  SSLCertificateFile    "C:/xampp/apache/conf/ssl/sunwukongzhubajjie.lat-chain.pem"
  SSLCertificateKeyFile "C:/xampp/apache/conf/ssl/sunwukongzhubajjie.lat-key.pem"

  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  Header always set X-Content-Type-Options "nosniff"

  <Directory "C:/xampp/apache/empty">
    Options None
    AllowOverride None
    Require all denied
  </Directory>

  # 根路径不提供任何页面
  <Location "/">
    Require all denied
  </Location>

  ProxyRequests Off
  ProxyPreserveHost On
  ProxyTimeout 300

  # WebSocket 反代，兼容 /api/ws/、/ws/、/ws
  ProxyPass        /api/ws/  ws://127.0.0.1:8080/
  ProxyPassReverse /api/ws/  ws://127.0.0.1:8080/

  ProxyPass        /ws/      ws://127.0.0.1:8080/
  ProxyPassReverse /ws/      ws://127.0.0.1:8080/

  ProxyPass        /ws       ws://127.0.0.1:8080/
  ProxyPassReverse /ws       ws://127.0.0.1:8080/

  ErrorLog  "logs/ws-ssl-error.log"
  CustomLog "logs/ws-ssl-access.log" common
</VirtualHost>
```

---

## 五、注意事项

### 1. `C:/xampp/apache/empty` 需要存在

如果目录不存在，需要创建：

```powershell
New-Item -ItemType Directory -Force "C:\xampp\apache\empty"
```

该目录应保持为空。

---

### 2. `Require local` 的含义

`Require local` 只允许本机访问，包括：

- `127.0.0.1`
- `::1`
- 本机本地连接

所以 `shark-admin.local`、`shark-control.local` 只适合本机调试。如果你需要局域网访问本地域名，需要改成指定 IP 白名单，例如：

```apache
Require ip 127.0.0.1 ::1 192.168.1.0/24
```

---

### 3. `/downloads` 的限制

推荐配置中，`/downloads` 只允许 `.apk` 文件。这样可以降低误放日志、配置、压缩包、数据库备份后被下载的风险。

如果未来改为鉴权下载，建议删除 Apache 的 `Alias /downloads`，改由后端接口校验权限后返回文件。

---

### 4. `AllowOverride None`

推荐将公开目录的：

```apache
AllowOverride All
```

改成：

```apache
AllowOverride None
```

原因：

- 避免 `.htaccess` 在子目录中改变安全策略；
- 所有规则集中在 `httpd-vhosts.conf` 中，方便审计；
- 性能也更好。

如果你确实依赖某些 `.htaccess`，需要逐条迁移到虚拟主机配置里。

---

## 六、修改后的验证流程

### 1. 检查 Apache 配置语法

```powershell
& "C:\xampp\apache\bin\httpd.exe" -t
```

期望输出：

```text
Syntax OK
```

常见错误：

#### `Invalid command 'Header'`

说明 `mod_headers` 未启用。

#### `Invalid command 'SSLEngine'`

说明 `mod_ssl` 未启用。

#### WebSocket 代理失败

检查 `mod_proxy_wstunnel` 是否启用。

---

### 2. 重启 Apache

根据之前记录，当前 Apache 不是 Windows 服务 `Apache2.4`，所以不要使用：

```powershell
& "C:\xampp\apache\bin\httpd.exe" -k restart
```

应该使用：

```text
XAMPP Control Panel -> Apache -> Stop -> Start
```

---

### 3. 验证 HTTPS 页面

浏览器访问：

```text
https://buyrdbcdgdst.cv
https://yybfok.store
```

预期：

- `buyrdbcdgdst.cv` 显示管理后台。
- `yybfok.store` 显示控制端。

---

### 4. 验证 HTTP 跳 HTTPS

访问：

```text
http://buyrdbcdgdst.cv
http://yybfok.store
http://sunwukongzhubajjie.lat
```

预期：自动跳转到 HTTPS。

---

### 5. 验证 WebSocket 域名

根路径：

```text
https://sunwukongzhubajjie.lat
```

预期：403 或不提供页面。

业务路径：

```text
wss://sunwukongzhubajjie.lat/api/ws/
wss://sunwukongzhubajjie.lat/ws
```

预期：业务 WebSocket 可连接。

---

### 6. 验证敏感文件不能访问

以下 URL 应返回 403 或 404：

```text
http://localhost/private/Eaod85401.php
http://localhost/shayukeji/shark-control-admin/backend/.env
http://localhost/shayukeji/shark-control/backend/.env
http://localhost/shayukeji/shark-control-backend/.env
http://localhost/shayukeji/db-backups-20260620_133231/clients.sql
http://localhost/api/ws/data/devices_backup.xlsx
http://localhost/api/ws/error_log.txt
http://localhost/shayukeji/shark-control/backend/keystores/
```

公网也建议测试：

```text
https://buyrdbcdgdst.cv/.env
https://buyrdbcdgdst.cv/package.json
https://buyrdbcdgdst.cv/src/
https://yybfok.store/.env
https://yybfok.store/package.json
https://yybfok.store/backend/
https://yybfok.store/downloads/
```

预期：

- 敏感文件全部 403 或 404。
- `/downloads/` 不能列目录。
- `/downloads` 下只有 `.apk` 可下载。

---

## 七、Apache 之外必须同步整改的事项

仅靠虚拟主机配置不是最终安全状态。建议继续做以下整改。

### 1. 迁出 Web 根目录

建议迁出：

```text
C:\xampp\htdocs\shayukeji\db-backups-20260620_133231
C:\xampp\htdocs\shayukeji\shark-control-admin\backend\.env
C:\xampp\htdocs\shayukeji\shark-control\backend\.env
C:\xampp\htdocs\shayukeji\shark-control-backend\.env
C:\xampp\htdocs\private
C:\xampp\htdocs\api\ws\data
C:\xampp\htdocs\api\ws\error_log.txt
C:\xampp\htdocs\shayukeji\shark-control\backend\keystores
C:\xampp\htdocs\shayukeji\shark-control\backend\logs
```

建议新位置：

```text
C:\xampp-appdata\shayukeji\config
C:\xampp-appdata\shayukeji\logs
C:\xampp-appdata\shayukeji\backups
C:\xampp-appdata\shayukeji\keystores
C:\xampp-appdata\shayukeji\ws-data
```

---

### 2. 后端服务改为生产环境

当前审查发现部分 `.env` 中存在：

```text
NODE_ENV=development
```

公网服务建议改为：

```text
NODE_ENV=production
```

并删除代码中的开发默认密钥兜底。

---

### 3. 轮换密钥

建议立即轮换：

- 数据库密码；
- JWT access secret；
- JWT refresh secret；
- 内部服务 Token；
- 旧 PHP 管理口令；
- 旧 PHP 加密 Key/IV，如果业务允许迁移。

---

### 4. 加固 WebSocket 服务

Apache 只能做反代，不能替代业务鉴权。WebSocket 服务还需要：

- 握手 Token；
- `phoneId` 与登录用户绑定；
- 设备端和网页端身份区分；
- 内部 HTTP 接口加 `X-Internal-Token` 和 IP 白名单；
- 命令下发审计日志。

---

### 5. 修复管理后台富文本 XSS

管理后台存在：

```vue
v-html="activeItem.content"
v-html="activeDoc.content"
```

建议前端使用 DOMPurify，后端保存前也做白名单清洗。

---

## 八、最小可执行整改清单

如果暂时不做完整迁移，至少立即完成：

1. 备份 `httpd-vhosts.conf`。
2. 注释 `NameVirtualHost *:80`。
3. 增加全局 `Directory C:/xampp/htdocs Require all denied`。
4. 单独放行两个前端 `dist`。
5. 公网 HTTP 加 301 跳 HTTPS。
6. `localhost` 不再指向整个 `htdocs`。
7. 禁止访问敏感文件和敏感目录。
8. `/downloads` 只允许 `.apk`。
9. `httpd.exe -t` 检查语法。
10. 用 XAMPP 控制面板重启 Apache。
11. 验证敏感文件全部 403/404。

---

## 九、最终结论

当前 `httpd-vhosts.conf` 的业务反代思路基本正确，但安全边界过宽。最核心的问题是：

```apache
DocumentRoot "C:/xampp/htdocs"
Require all granted
Options Indexes
```

这会让整个 `htdocs` 成为潜在暴露面。

最终应达到：

> Apache 只暴露管理后台前端 `dist`、控制端前端 `dist`、受限 APK 下载路径和 WebSocket 反代；其余源码、后端、配置、备份、日志、keystore、数据库文件全部不允许 Web 访问，最好迁出 `htdocs`。
