概述
本网站使用手写自定义实现(非第三方 auth 模块)支持 GitHub 账号登录。登录后自动使用 GitHub 用户名和头像创建/关联本地用户。配置分为本地测试和线上部署两套独立的 OAuth App。
技术架构
- 服务端路由:
server/api/auth/github.get.ts(发起授权)、server/api/auth/github/callback.get.ts(处理回调) - 认证工具:
server/utils/auth.ts(JWT 签发/验证、bcrypt 密码哈希) - 用户存储:
.data/db/users.json(JSON 文件数据库,通过server/utils/db.ts读写) - 环境变量:通过
nuxt.config.ts的runtimeConfig读取,Docker 运行时通过-e注入
第一步:创建 GitHub OAuth App
- 打开 GitHub Developer Settings
- 点击 OAuth Apps → New OAuth App
- 填写表单:
| 字段 | 本地测试 | 线上部署 |
|---|---|---|
| Application name | 我的网站-本地 |
我的网站 |
| Homepage URL | http://localhost:3000 |
https://yibi2333.fun |
| Authorization callback URL | http://localhost:3000/api/auth/github/callback |
https://yibi2333.fun/api/auth/github/callback |
- 点击 Register application
- 记录 Client ID
- 点击 Generate a new client secret,立即复制 Client Secret(只显示一次)
💡 本地和线上各建一个 OAuth App,互不干扰。
第二步:配置环境变量
本地测试
项目根目录 .env 文件(已加入 .gitignore):
bash
# GitHub OAuth(本地)
NUXT_GITHUB_CLIENT_ID=本地ClientID
NUXT_GITHUB_CLIENT_SECRET=本地ClientSecret
NUXT_GITHUB_REDIRECT_URI=http://localhost:3000/api/auth/github/callback
NUXT_PUBLIC_SITE_URL=http://localhost:3000线上部署
使用 Docker 部署,环境变量通过 -e 参数注入:
bash
docker run -d --name nuxt \
-v /data/nuxt/.output:/app \
-w /app \
-p 3000:3000 \
--restart unless-stopped \
-e NUXT_GITHUB_CLIENT_ID=线上ClientID \
-e NUXT_GITHUB_CLIENT_SECRET=线上ClientSecret \
-e NUXT_GITHUB_REDIRECT_URI=https://yibi2333.fun/api/auth/github/callback \
-e NUXT_PUBLIC_SITE_URL=https://yibi2333.fun \
node:20-alpine node server/index.mjs环境变量说明:
| 变量 | 用途 | 注意 |
|---|---|---|
NUXT_GITHUB_CLIENT_ID |
OAuth App Client ID | 服务端可读 |
NUXT_GITHUB_CLIENT_SECRET |
OAuth App Client Secret | 服务端可读,不可泄露 |
NUXT_GITHUB_REDIRECT_URI |
回调地址 | 必须与 OAuth App 设置完全一致 |
NUXT_PUBLIC_SITE_URL |
站点地址 | public 变量需 NUXT_PUBLIC_ 前缀 |
⚠️runtimeConfig.public.*的环境变量必须用NUXT_PUBLIC_前缀,NUXT_SITE_URL是错误写法。
第三步:验证
- 部署后检查容器环境变量:
- 打开网站登录页,点击 GitHub 登录 按钮
- 跳转到 GitHub 授权页面 → 确认授权
- 自动跳转回网站,完成登录
bash
docker exec nuxt env | grep NUXT登录流程
用户点击 GitHub 登录
→ GET /api/auth/github(生成 state 防 CSRF,302 跳转 GitHub)
→ GitHub 授权页(用户确认)
→ 302 回调 /api/auth/github/callback?code=xxx&state=xxx
→ 服务端校验 state,用 code 换取 access_token
→ 获取 GitHub 用户信息(GET api.github.com/user)
→ 查找/创建本地用户记录(users.json)
→ 签发 JWT Token
→ 302 重定向到 /auth/github-callback?token=JWT&username=...
→ 前端页面保存 Token 到 localStorage,跳转首页安全说明
Client Secret只存在服务端,不暴露给前端- OAuth state 参数(UUID v4,5 分钟过期)防 CSRF 攻击
- 用户密码不存储(GitHub 验证)
.env文件已在.gitignore中,不会提交到仓库- JWT 密钥从环境变量
NUXT_JWT_SECRET读取,无环境变量时使用硬编码默认值
常见问题
callback 返回 500 错误
通常是因为 .data/db/ 下的 JSON 文件损坏。检查方法:
bash
# 诊断所有 JSON 文件
docker exec nuxt node -e "
const fs = require('fs');
fs.readdirSync('/app/.data/db').filter(f => f.endsWith('.json')).forEach(f => {
const p = '/app/.data/db/' + f;
try { JSON.parse(fs.readFileSync(p,'utf8')); console.log(f + ' OK'); }
catch(e) { console.log(f + ' BROKEN: ' + e.message); }
});
"常见 JSON 损坏类型:
//注释行 — JSON 不支持注释- 尾部逗号:
{ ... },后跟]— 最后一个元素后不能有逗号
一键修复:
bash
docker exec nuxt node -e "
const fs = require('fs');
const dir = '/app/.data/db';
fs.readdirSync(dir).filter(f => f.endsWith('.json')).forEach(f => {
const p = dir + '/' + f;
try { JSON.parse(fs.readFileSync(p,'utf8')); }
catch(e) {
let raw = fs.readFileSync(p,'utf8');
let data = eval('(' + raw + ')');
fs.writeFileSync(p, JSON.stringify(data, null, 2), 'utf8');
console.log(f + ' FIXED');
}
});
"redirect_uri 不匹配
确保 OAuth App 后台的 Authorization callback URL 与 NUXT_GITHUB_REDIRECT_URI 完全一致
容器重启后登录失败
检查数据库挂载是否持久化(需要挂载 .data/db 目录)
后续优化
- [x] GitHub 登录
- [x] 自动同步头像和用户名
- [ ] 绑定已有账号
- [ ] 解绑 GitHub 账号