Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dd6ca87
fix(auth): stop exposing refresh token in login JSON
xiaoqianran Aug 18, 2026
2bb37ea
feat(auth): persist refresh token in HttpOnly cookie
xiaoqianran Aug 18, 2026
70c37d1
feat(auth): validate browser origins for cookie auth endpoints
xiaoqianran Aug 18, 2026
865ba4d
feat(security): enforce service key for every internal endpoint
xiaoqianran Aug 18, 2026
5c62462
refactor(security): centralize internal and browser auth filters
xiaoqianran Aug 18, 2026
9d8e4be
feat(auth): rotate refresh tokens through HttpOnly cookie
xiaoqianran Aug 18, 2026
0ebe7dc
refactor(security): remove controller-level internal auth convention
xiaoqianran Aug 18, 2026
2ab9ca4
config(auth): harden browser session defaults
xiaoqianran Aug 18, 2026
220d809
config(auth): allow HttpOnly refresh cookie on local HTTP
xiaoqianran Aug 18, 2026
39ca19c
fix(frontend): keep access token in memory and refresh via cookie
xiaoqianran Aug 18, 2026
5140e66
fix(frontend): restore sessions only through HttpOnly refresh cookie
xiaoqianran Aug 18, 2026
c999366
fix(frontend): restore cookie session before route authorization
xiaoqianran Aug 18, 2026
db0e177
refactor(frontend): remove refresh token from login contract
xiaoqianran Aug 18, 2026
411b65e
feat(gateway): add credential-safe exact-origin CORS filter
xiaoqianran Aug 18, 2026
cab6e1a
config(gateway): enable exact-origin credential CORS and short access…
xiaoqianran Aug 18, 2026
c5e104d
docs(security): document HttpOnly cookie and credential CORS deployment
xiaoqianran Aug 18, 2026
21e32c2
test(auth): cover HttpOnly refresh cookie contract
xiaoqianran Aug 18, 2026
6c794ca
test(auth): reject untrusted browser origins
xiaoqianran Aug 18, 2026
1f0f384
test(security): prove all internal paths require service key
xiaoqianran Aug 18, 2026
a2d88b3
test(frontend): assert tokens stay out of Web Storage
xiaoqianran Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 114 additions & 170 deletions docs/deployment/github-pages-caddy-api.md
Original file line number Diff line number Diff line change
@@ -1,267 +1,211 @@
# GitHub Pages 前端 + Caddy 后端部署

本文只针对本项目 `Java006-CampusHub` 的前后端分离部署:
本文针对 `Java-006-CampusHub` 的前后端分离部署:

- 前端:GitHub Actions 构建 Vue 项目,并部署到 GitHub Pages
- 后端:运行在你的服务器上
- 服务器入口:Caddy + 后端 API 域名
- 后端网关:`shiqian-gateway`,本机端口 `8080`
- 前端:GitHub Pages
- API:`https://api.xiaoqianran.xyz`
- Caddy:TLS + 反向代理
- API 网关:`shiqian-gateway`(本机 `8080`

## 推荐域名结构
> 认证安全模型已经调整:Access Token 仅保存在页面内存,Refresh Token 仅保存在 `HttpOnly` Cookie。浏览器请求 API 必须携带 credentials,并由 Gateway 统一处理精确 Origin CORS。

建议用一个独立 API 子域名给后端:
## 推荐域名结构

| 用途 | 示例 |
|------|------|
| GitHub Pages 前端 | `https://<你的GitHub用户名>.github.io/Java006-CampusHub/` |
| GitHub Pages 自定义前端域名 | `https://shiqian.xiaoqianran.xyz` |
|---|---|
| GitHub Pages 默认域名 | `https://<user>.github.io/Java-006-CampusHub/` |
| 自定义前端域名 | `https://shiqian.xiaoqianran.xyz` |
| 后端 API 域名 | `https://api.xiaoqianran.xyz` |

前端所有接口请求都访问:

```text
https://api.xiaoqianran.xyz/api/...
```

后端服务器上,Caddy 只需要把 `api.xiaoqianran.xyz` 反代到网关:
前端 API 地址:

```text
127.0.0.1:8080
https://api.xiaoqianran.xyz
```

## 服务端口

| 服务 | 端口 | 是否建议公网暴露 | 说明 |
|------|------|------------------|------|
| `shiqian-gateway` | `8080` | 是,仅通过 Caddy 暴露 | 前端所有 `/api/*` 请求进入这里 |
| `shiqian-user` | `8081` | 否 | 网关内部转发用户接口 |
| `shiqian-resource` | `8082` | 否 | 网关内部转发资源和分类接口 |
| MySQL | `3306` | 否 | 数据库 |
| Redis | `6379` | 否 | 缓存 |
| Nacos | `8848` | 否 | 服务发现/配置 |
| Elasticsearch | `9200` | 否 | 搜索 |
| RabbitMQ | `5672` / `15672` | 否 | 消息队列和管理页 |

## 服务器 Caddyfile

下面是后端 API 域名的 Caddy 配置。把 `Access-Control-Allow-Origin` 改成你的 GitHub Pages 前端地址。
| 服务 | 端口 | 公网暴露 |
|---|---:|---|
| `shiqian-gateway` | 8080 | 仅通过 Caddy |
| `shiqian-user` | 8081 | 否 |
| `shiqian-resource` | 8082 | 否 |
| MySQL | 3306 | 否 |
| Redis | 6379 | 否 |
| Nacos | 8848 | 否 |
| Elasticsearch | 9200 | 否 |
| RabbitMQ | 5672 / 15672 | 否 |

如果你使用 GitHub Pages 默认地址:
## Caddyfile

```text
https://<你的GitHub用户名>.github.io
```

如果你给 GitHub Pages 绑定了自定义域名,例如:

```text
https://shiqian.xiaoqianran.xyz
```

就填这个自定义域名。
CORS 已由 `shiqian-gateway` 统一处理,Caddy **不要再次写 `Access-Control-*` 响应头**,否则容易产生重复/冲突头。

```caddyfile
# ====================== shiqian api ======================
api.xiaoqianran.xyz {
encode gzip zstd

@preflight method OPTIONS
respond @preflight 204

header {
Access-Control-Allow-Origin "https://<你的前端Pages域名>"
Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS"
Access-Control-Allow-Headers "Authorization,Content-Type"
Access-Control-Max-Age "86400"
Vary "Origin"
}

reverse_proxy 127.0.0.1:8080
}
```

示例:如果前端是 GitHub Pages 默认地址:

```caddyfile
api.xiaoqianran.xyz {
encode gzip zstd

@preflight method OPTIONS
respond @preflight 204

header {
Access-Control-Allow-Origin "https://你的GitHub用户名.github.io"
Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS"
Access-Control-Allow-Headers "Authorization,Content-Type"
Access-Control-Max-Age "86400"
Vary "Origin"
}
重载:

reverse_proxy 127.0.0.1:8080
}
```bash
caddy reload --config /etc/caddy/Caddyfile
```

示例:如果前端 GitHub Pages 绑定自定义域名 `shiqian.xiaoqianran.xyz`:
## 认证 Cookie 与 Origin 配置

```caddyfile
api.xiaoqianran.xyz {
encode gzip zstd

@preflight method OPTIONS
respond @preflight 204
### 场景 A:GitHub Pages 默认域名

header {
Access-Control-Allow-Origin "https://shiqian.xiaoqianran.xyz"
Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS"
Access-Control-Allow-Headers "Authorization,Content-Type"
Access-Control-Max-Age "86400"
Vary "Origin"
}
例如:

reverse_proxy 127.0.0.1:8080
}
```text
Frontend: https://xiaoqianran.github.io
API: https://api.xiaoqianran.xyz
```

重载 Caddy
两者属于跨站点。生产环境

```bash
caddy reload --config /etc/caddy/Caddyfile
CORS_ALLOWED_ORIGINS=https://xiaoqianran.github.io
BROWSER_AUTH_ALLOWED_ORIGINS=https://xiaoqianran.github.io
REFRESH_TOKEN_COOKIE_SECURE=true
REFRESH_TOKEN_COOKIE_SAME_SITE=None
```

## GitHub Actions Pages 配置
`SameSite=None` 必须和 `Secure=true` 一起使用。

仓库已经有前端 Pages workflow:
### 场景 B:自定义同站前端子域

例如:

```text
.github/workflows/deploy-frontend-pages.yml
Frontend: https://shiqian.xiaoqianran.xyz
API: https://api.xiaoqianran.xyz
```

### 当前 workflow 主要改进(2026 年更新)
推荐:

- 使用 `actions/configure-pages@v4` 初始化 Pages 环境(官方推荐)
- 自动生成 `.nojekyll` 文件,避免 GitHub Pages 的 Jekyll 处理导致的资源 404 问题
- `VITE_BASE` 使用 `github.event.repository.name`(GitHub Actions 表达式支持的稳定写法)
- 构建时优先读取仓库 Variables 中的 `VITE_API_BASE_URL`
```bash
CORS_ALLOWED_ORIGINS=https://shiqian.xiaoqianran.xyz
BROWSER_AUTH_ALLOWED_ORIGINS=https://shiqian.xiaoqianran.xyz
REFRESH_TOKEN_COOKIE_SECURE=true
REFRESH_TOKEN_COOKIE_SAME_SITE=Lax
```

### 配置步骤
如果需要同时允许多个前端 Origin,使用英文逗号分隔:

1. 进入仓库 `Settings -> Secrets and variables -> Actions -> Variables`,新增仓库变量:
```bash
CORS_ALLOWED_ORIGINS=https://xiaoqianran.github.io,https://shiqian.xiaoqianran.xyz
BROWSER_AUTH_ALLOWED_ORIGINS=https://xiaoqianran.github.io,https://shiqian.xiaoqianran.xyz
```

**变量名**:`VITE_API_BASE_URL`

**变量值**:`https://你的真实API域名`(例如 `https://api.xiaoqianran.xyz`)
禁止配置:

> **重要**:不配置时会回退到示例域名,可能导致前端无法与你的后端交互(出现“系统内部错误”等)。
```text
CORS_ALLOWED_ORIGINS=*
```

2. 进入 `Settings -> Pages`,将 `Build and deployment` 的 `Source` 设置为 **GitHub Actions**
因为本项目启用了 credential cookie,`*` 既不安全,也与 credential CORS 语义冲突

3. 推送到 `main` 分支或手动触发 `Deploy Frontend to GitHub Pages` workflow 即可自动部署。
## JWT 建议

workflow 现在更贴近 GitHub 官方 Pages 部署最佳实践,部署成功率和稳定性显著提升。
生产环境至少显式配置:

## GitHub Pages 路径说明
```bash
JWT_SECRET=<高熵随机密钥,至少 32 字节>
JWT_ACCESS_TOKEN_EXPIRATION=1800000
JWT_REFRESH_TOKEN_EXPIRATION=604800000
```

当前 `deploy-frontend-pages.yml` workflow 使用以下表达式(GitHub Actions 官方支持的写法):
默认 Access Token 为 30 分钟,Refresh Token 为 7 天。Refresh Token 不会出现在登录/刷新 JSON 中,也不会进入 `localStorage`。

```yaml
VITE_BASE: /${{ github.event.repository.name }}/
```
## GitHub Pages 配置

这适合 GitHub Pages 默认项目地址,兼容 push 和手动触发
仓库使用

```text
https://<你的GitHub用户名>.github.io/Java006-CampusHub/
.github/workflows/deploy-frontend-pages.yml
```

如果你给 Pages 绑定了自定义域名,并且网站在域名根路径访问,例如

```text
https://shiqian.xiaoqianran.xyz/
Settings -> Secrets and variables -> Actions -> Variables
```

可以手动把 workflow 里的 `VITE_BASE` 改成
设置

```yaml
VITE_BASE: /
```text
VITE_API_BASE_URL=https://api.xiaoqianran.xyz
```

否则静态资源路径会多一层仓库名。

**注意**:workflow 内部已自动处理,无需在大多数场景下手动修改。注意 GitHub Actions 表达式语法有限,不支持 `.split()` 等 JS 方法。

## 前端如何指定后端地址

本项目前端支持两种后端地址配置。

### 方式一:GitHub Actions 变量,推荐用于 Pages

在 GitHub Actions Variables 中设置:
然后在:

```text
VITE_API_BASE_URL=https://api.xiaoqianran.xyz
Settings -> Pages
```

然后重新运行 `Deploy Frontend to GitHub Pages` workflow
选择 GitHub Actions 作为部署源

### 方式二:运行时 config.js
## Pages 路径

本地或自托管静态文件时,可以编辑
默认项目 Pages 地址使用

```text
shiqian-frontend/public/config.js
```yaml
VITE_BASE: /${{ github.event.repository.name }}/
```

或构建后的
如果绑定自定义域名并从域名根目录访问,可将构建路径设置为

```text
shiqian-frontend/dist/config.js
```yaml
VITE_BASE: /
```

示例:
## 运行时 API 地址

前端也支持:

```js
window.__SHIQIAN_CONFIG__ = {
apiBaseUrl: 'https://api.xiaoqianran.xyz'
}
```

注意:GitHub Pages 上的 `config.js` 来自仓库构建产物,不能直接登录服务器修改;Pages 场景优先使用 GitHub Actions Variables。
对应:

## 前端本地开发代理
```text
shiqian-frontend/public/config.js
```

本地开发默认代理到:
Pages 场景优先使用 Actions Variables。

## 本地开发

默认 Vite 代理目标:

```text
http://localhost:8080
```

如果本地前端要连服务器后端
也可指定

```bash
cd shiqian-frontend
VITE_API_PROXY_TARGET=https://api.xiaoqianran.xyz npm run dev -- --host 0.0.0.0
```

本地开发时,浏览器请求 `/api/...`,Vite 开发服务器会把请求代理到 `VITE_API_PROXY_TARGET`。

## 部署检查清单

1. 后端服务器启动 `shiqian-gateway`,确认监听 `8080`。
2. DNS 添加 `api.xiaoqianran.xyz`,指向你的服务器公网 IP。
3. Caddy 使用上面的 `api.xiaoqianran.xyz` 配置(正确设置 CORS)。
4. Caddy 的 `Access-Control-Allow-Origin` 填你的 GitHub Pages 前端域名(或使用 `*` 临时测试)。
5. **GitHub 仓库设置**:
- `Settings -> Pages` → Source 选择 **GitHub Actions**
- `Settings -> Secrets and variables -> Actions -> Variables` 新增 `VITE_API_BASE_URL`(**强烈建议配置**)
6. 推送代码到 `main` 或手动触发 `Deploy Frontend to GitHub Pages` workflow。
7. 部署成功后,在浏览器开发者工具 Network 面板确认接口请求正确发往你的后端域名。

## 本项目已改动的前端文件

| 文件 | 作用 |
|------|------|
| `shiqian-frontend/public/config.js` | 运行时后端地址配置,Pages 场景通常保持空值并使用 GitHub Actions Variables |
| `shiqian-frontend/index.html` | 加载 `/config.js` |
| `shiqian-frontend/src/api/client.ts` | 优先读取运行时配置,其次读取 `VITE_API_BASE_URL` |
| `shiqian-frontend/src/env.d.ts` | 声明 `window.__SHIQIAN_CONFIG__` 类型 |
| `shiqian-frontend/vite.config.ts` | 开发代理支持 `VITE_API_PROXY_TARGET` |
本地 `shiqian-user` profile 会把 Refresh Cookie 的 `Secure` 关闭,以允许 `http://localhost` 开发;不要把 local profile 用于生产。

## 上线检查清单

1. `shiqian-user`、`shiqian-resource`、数据库、Redis、Nacos 等内部端口不对公网开放。
2. Caddy 只反向代理到 Gateway `127.0.0.1:8080`。
3. `JWT_SECRET`、`INTERNAL_SERVICE_KEY` 使用独立高熵随机值。
4. `CORS_ALLOWED_ORIGINS` 与 `BROWSER_AUTH_ALLOWED_ORIGINS` 都是精确 HTTPS Origin,不能是 `*`。
5. GitHub Pages 默认域名部署使用 `REFRESH_TOKEN_COOKIE_SAME_SITE=None` + `REFRESH_TOKEN_COOKIE_SECURE=true`。
6. 自定义同站子域优先使用 `SameSite=Lax` + `Secure=true`。
7. 浏览器 Network 中登录响应应出现 `Set-Cookie: campushub_refresh=...; HttpOnly; Secure; ...`。
8. 登录/刷新响应 JSON 中不得出现 `refreshToken`。
9. Application -> Local Storage 中不得出现 `shiqian_access_token` 或 `shiqian_refresh_token`。
10. 刷新页面后,应通过一次 `/api/user/refresh` Cookie 请求恢复登录,而不是从 Web Storage 恢复 token。
Loading
Loading