Last updated on
OAuth 学习笔记 - Decap CMS 认证原理与实现
一、OAuth 是什么?
1.1 定义
OAuth (Open Authorization) 是一种开放标准授权协议,允许用户让第三方应用访问他们在其他服务上的资源,而不需要将用户名和密码提供给第三方应用。
1.2 核心概念
- 资源拥有者 (Resource Owner):能够授权访问受保护资源的实体(通常是用户)
- 客户端 (Client):代表资源拥有者请求访问受保护资源的第三方应用(Decap CMS)
- 授权服务器 (Authorization Server):验证用户身份并颁发访问令牌的服务器(GitHub)
- 资源服务器 (Resource Server):托管受保护资源的服务器(GitHub API)
1.3 关键术语
- Client ID:客户端标识符,公开可见
- Client Secret:客户端密钥,必须保密,不能暴露在客户端代码中
- Access Token:访问令牌,用于访问受保护资源
- Authorization Code:授权码,临时授权凭证,用于换取 Access Token
二、OAuth 认证流程详解
2.1 标准 OAuth 2.0 授权码模式流程
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Decap CMS │ │ GitHub OAuth │ │ GitHub │
│ (Client) │ │ Authorization │ │ API │
└──────┬──────┘ └────────┬─────────┘ └──────┬──────┘
│ │ │
│ 1. 访问 /admin │ │
│ → 检测未登录 │ │
│ │ │
│ 2. 重定向到 GitHub OAuth │ │
│ → https://github.com/login/ │ │
│ oauth/authorize?client_id= │ │
│ XXX&redirect_uri=XXX& │ │
│ scope=repo&state=XXX │ │
│────────────────────────────────>│ │
│ │ 3. 显示登录页面 │
│ │────────────────────────────────>│
│ │ 4. 用户授权 │
│ │<────────────────────────────────│
│ 5. 重定向回回调 URL │ │
│ ← https://your-site.com/admin/ │ │
│ ?code=AUTH_CODE&state=XXX │ │
│<────────────────────────────────│ │
│ │ │
│ 6. 用授权码换取 Access Token │ │
│ → POST https://github.com/ │ │
│ login/oauth/access_token │ │
│ (包含 client_secret) │ │
│────────────────────────────────>│ │
│ │ 7. 验证授权码,颁发 Token │
│ 8. 返回 Access Token │ │
│ ← {"access_token": "ghp_...", │ │
│ "token_type": "bearer", │ │
│ "scope": "repo"} │ │
│<────────────────────────────────│ │
│ │ │
│ 9. 使用 Token 访问 GitHub API │ │
│ → Authorization: bearer ghp_...│ │
│────────────────────────────────>│────────────────────────────────>│
│ 10. 返回仓库数据 │ │
│ ← [repo list, content, etc] │<────────────────────────────────│
│<────────────────────────────────│ │
2.2 流程步骤说明
- 用户访问 /admin:Decap CMS 检测用户未登录
- 重定向到 GitHub OAuth:携带
client_id、redirect_uri、scope、state等参数 - 显示登录页面:GitHub 要求用户登录并授权
- 用户授权:用户同意授予访问权限
- 重定向回回调 URL:GitHub 携带
code(授权码)和state回到你的站点 - 用授权码换取 Access Token:客户端发送
client_id、client_secret、code到 GitHub - 验证并颁发 Token:GitHub 验证授权码有效,返回
access_token - 返回 Access Token:客户端获得访问令牌
- 访问 GitHub API:使用
access_token访问受保护资源 - 返回数据:GitHub API 返回仓库内容
三、Decap CMS 的 OAuth 实现
3.1 项目架构
你的项目(EdwainPhilo.github.io)
├── src/pages/admin.astro # CMS 入口页面
├── public/admin/config.yml # CMS 配置文件
└── src/content/blog/ # 博客内容存储
托管平台:GitHub Pages
OAuth 服务:GitHub OAuth App
3.2 关键配置文件
src/pages/admin.astro
---
---
<link href="/admin/config.yml" type="text/yaml" rel="cms-config-url">
<script src="https://unpkg.com/decap-cms@^3.1.0/dist/decap-cms.js"></script>
<div id="nc-root"></div>
public/admin/config.yml
backend:
name: github
repo: EdwainPhilo/EdwainPhilo.github.io
branch: main
# 基础 URL:OAuth 代理服务器地址
base_url: https://your-oauth-proxy.vercel.app
media_folder: "src/assets"
public_folder: "/assets"
collections:
- name: "blog"
label: "博客"
folder: "src/content/blog"
create: true
slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
fields:
- {label: "标题", name: "title", widget: "string"}
- {label: "发布日期", name: "date", widget: "datetime"}
- {label: "内容", name: "body", widget: "markdown"}
3.3 Decap CMS 的默认行为
- 默认使用 Netlify Identity 服务:
https://api.netlify.com/auth - 但你的站点托管在 GitHub Pages,Netlify 不认识这个域名
- 导致 OAuth 认证流程中断,返回 404 错误
四、本地环境 vs 线上环境的区别
4.1 本地环境(localhost)为什么能工作?
原因分析
本地环境流程:
1. 访问 http://localhost:4321/admin
2. Decap CMS 检测到 localhost
3. 可以直接处理 GitHub OAuth 回调
4. 使用 postMessage 通信机制
5. 无需代理服务器也能完成认证
关键点
- 浏览器安全策略:
localhost被视为特殊域名 - 开发模式:许多工具为本地开发做了特殊处理
- 直接通信:Decap CMS 可以直接处理 OAuth 回调
- postMessage 机制:使用
window.postMessage进行跨窗口通信
// Decap CMS 内部的 OAuth 处理(简化)
window.addEventListener('message', (event) => {
if (event.data.type === 'OAUTH_SUCCESS') {
const { access_token } = event.data;
// 保存 token,完成登录
}
});
4.2 线上环境(GitHub Pages)为什么 404?
原因分析
线上环境流程:
1. 访问 https://yourname.github.io/admin
2. Decap CMS 检测未登录
3. 尝试重定向到 OAuth 认证
4. 问题出现:
a. Netlify Identity 不认识 yourname.github.io 域名
b. 浏览器阻止跨域请求(CORS)
c. Client Secret 不能暴露在浏览器中
5. 返回 404 错误
核心问题
-
跨域问题(CORS)
浏览器同源策略: - yourname.github.io 发起的请求 - 目标:api.netlify.com - 不同源 → 浏览器拦截 -
Client Secret 安全问题
严重安全隐患: ❌ 将 Client Secret 放在前端代码中 ❌ 用户可以看到:var CLIENT_SECRET = "your-secret-key"; ❌ 任何人都可以用这个密钥伪造认证 -
域名认证问题
GitHub OAuth App 配置: - 回调 URL 必须预先配置 - GitHub Pages 的 OAuth 回调需要特殊处理 - 默认的 Netlify Identity 无法识别 GitHub Pages 域名
五、为什么需要代理服务器?
5.1 核心原因
1. 保护 Client Secret
传统方式(不安全):
浏览器 → 直接请求 GitHub API
携带 client_secret(暴露在代码中)❌
使用代理服务器(安全):
浏览器 → OAuth 代理服务器 → GitHub API
不携带 client_secret ✓
代理服务器保管 secret ✓
2. 解决 CORS 问题
直接请求(被拦截):
yourname.github.io → api.netlify.com
(跨域,浏览器拦截)❌
通过代理(解决):
yourname.github.io → oauth-proxy.vercel.app(同域)✓
→ api.netlify.com(服务器间请求,不受浏览器限制)✓
3. 完成 OAuth 流程
OAuth 流程需要服务器端操作:
1. 接收授权码(code)
2. 验证 state(防止 CSRF)
3. 用 code 换取 access_token
4. 存储 token
5. 返回给客户端
这些操作必须在服务器端完成,不能在浏览器中执行
5.2 形象比喻
比喻一:酒店寄存行李
传统方式(不安全):
你(浏览器)直接把行李(Client Secret)给快递员(GitHub API)
→ 快递员可能不认识你,行李可能丢失 ❌
使用代理(安全):
你 → 酒店前台(代理服务器)→ 快递员
→ 前台验证你的身份,帮你寄存行李 ✓
→ 快递员只和酒店打交道,不直接接触你 ✓
比喻二:装修工人入场
传统方式(不安全):
装修工人(Decap CMS)直接找物业(GitHub)要钥匙
→ 物业不认识工人,不给钥匙 ❌
使用代理(安全):
工人 → 包工头(代理服务器)→ 物业
→ 包工头有合法的授权,向物业领钥匙 ✓
→ 包工头监督工人使用钥匙 ✓
六、解决方案详解
6.1 方案一:Vercel OAuth 服务器(推荐)
优点
- ✅ 完全免费
- ✅ 稳定可靠
- ✅ 配置简单
- ✅ 自动 HTTPS
- ✅ 全球 CDN
步骤 1:部署 OAuth 代理服务器
// oauth-proxy.js
module.exports = async (req, res) => {
const { code, state } = req.query;
// 用授权码换取 access_token
const response = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
client_id: process.env.GITHUB_CLIENT_ID,
client_secret: process.env.GITHUB_CLIENT_SECRET,
code: code,
}),
});
const data = await response.json();
// 返回包含 access_token 的 HTML
res.setHeader('Content-Type', 'text/html');
res.send(`
<!DOCTYPE html>
<html>
<head>
<script>
// 将 token 发送给父窗口
window.opener.postMessage({
type: 'OAUTH_SUCCESS',
access_token: '${data.access_token}',
token_type: '${data.token_type}'
}, '*');
window.close();
</script>
</head>
<body>OAuth 认证成功,正在关闭...</body>
</html>
`);
};
// vercel.json
{
"functions": {
"api/oauth.js": {
"memory": 1024
}
}
}
// package.json
{
"name": "decap-cms-oauth-proxy",
"version": "1.0.0",
"dependencies": {}
}
步骤 2:配置环境变量
在 Vercel 项目设置中添加:
GITHUB_CLIENT_ID: 你的 GitHub OAuth App 的 Client IDGITHUB_CLIENT_SECRET: 你的 GitHub OAuth App 的 Client Secret
步骤 3:部署到 Vercel
# 安装 Vercel CLI
npm i -g vercel
# 登录
vercel login
# 部署
vercel
# 配置环境变量(在 Vercel 控制台添加)
步骤 4:更新 CMS 配置
# public/admin/config.yml
backend:
name: github
repo: EdwainPhilo/EdwainPhilo.github.io
branch: main
base_url: https://your-oauth-proxy.vercel.app
# 完整的授权 URL
auth_endpoint: https://github.com/login/oauth/authorize
# 代理服务器的回调端点
auth_url: https://your-oauth-proxy.vercel.app/api/auth
media_folder: "src/assets"
public_folder: "/assets"
collections:
# ... 你的集合配置
6.2 方案二:Cloudflare Worker OAuth 代理
优点
- ✅ 极其轻量
- ✅ 快速响应
- ✅ 完全免费
- ✅ 边缘计算
实现代码
// oauth-proxy.worker.js
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
// OAuth 回调处理
if (url.pathname === '/auth/callback') {
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
// 用授权码换取 access_token
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'User-Agent': 'Decap-CMS-OAuth-Proxy',
},
body: JSON.stringify({
client_id: GITHUB_CLIENT_ID,
client_secret: GITHUB_CLIENT_SECRET,
code: code,
}),
});
const tokenData = await tokenResponse.json();
// 返回包含 token 的 HTML
return new Response(`
<!DOCTYPE html>
<html>
<head>
<script>
window.opener.postMessage({
type: 'OAUTH_SUCCESS',
access_token: '${tokenData.access_token}',
token_type: '${tokenData.token_type}'
}, '*');
window.close();
</script>
</head>
<body>OAuth 认证成功</body>
</html>
`, {
headers: { 'Content-Type': 'text/html' },
});
}
return new Response('Not Found', { status: 404 });
}
部署步骤
# 安装 Wrangler CLI
npm install -g wrangler
# 登录
wrangler login
# 创建 Worker
wrangler init oauth-proxy
# 配置环境变量
wrangler secret put GITHUB_CLIENT_ID
wrangler secret put GITHUB_CLIENT_SECRET
# 部署
wrangler deploy oauth-proxy.worker.js
6.3 方案三:本地开发模式(临时测试)
配置方式
# public/admin/config.yml
backend:
name: github
repo: EdwainPhilo/EdwainPhilo.github.io
branch: main
# 本地开发模式
local_backend: true
media_folder: "src/assets"
public_folder: "/assets"
collections:
# ... 你的集合配置
注意事项
- ⚠️ 仅适用于本地开发
- ⚠️ 不适用于线上环境
- ⚠️ 需要启动本地开发服务器
- ⚠️ 需要安装
netlify-cli
# 安装 netlify-cli
npm install -g netlify-cli
# 启动开发模式
netlify-cms-proxy-server
七、常见问题与注意事项
7.1 GitHub OAuth App 配置
必须配置的参数
Application name: Decap CMS OAuth
Homepage URL: https://yourname.github.io
Application description: OAuth for Decap CMS
Authorization callback URL: https://your-oauth-proxy.vercel.app/api/auth/callback
权限范围(Scope)
repo - 访问私有仓库
public_repo - 访问公开仓库(推荐用于 GitHub Pages)
7.2 常见错误与解决
错误 1:redirect_uri_mismatch
错误信息:
{"error": "redirect_uri_mismatch", "error_description": "..."}
原因:
回调 URL 与 GitHub OAuth App 配置不一致
解决:
1. 检查 GitHub OAuth App 的 Authorization callback URL
2. 确保与代理服务器的 URL 完全一致
3. 注意协议(http vs https)
错误 2:404 Not Found
错误信息:
访问 /admin 时返回 404
原因:
1. base_url 配置错误
2. 代理服务器未部署或访问失败
3. GitHub OAuth App 配置错误
解决:
1. 检查 base_url 是否正确
2. 确认代理服务器可以访问
3. 检查 GitHub OAuth App 配置
错误 3:CORS 错误
错误信息:
Access to fetch at '...' has been blocked by CORS policy
原因:
跨域请求被浏览器拦截
解决:
1. 使用代理服务器
2. 配置 CORS 头部
3. 确保代理服务器返回正确的 CORS 头
7.3 安全最佳实践
✅ 应该做的
- 在服务器端存储 Client Secret
- 使用 HTTPS
- 验证 state 参数(防止 CSRF)
- 设置合理的 token 过期时间
- 限制 OAuth App 的权限范围
❌ 不应该做的
- 将 Client Secret 放在前端代码中
- 在 URL 中传递敏感信息
- 忽略 CORS 和同源策略
- 使用过于宽松的权限范围
7.4 调试技巧
1. 浏览器开发者工具
// Console 中查看 postMessage
window.addEventListener('message', (event) => {
console.log('收到消息:', event.data);
});
// Network 标签查看请求
// 检查 OAuth 流程中的每个请求
2. 检查 localStorage
// 查看存储的 token
console.log(localStorage.getItem('decap-cms-user'));
// 查看认证状态
console.log(localStorage.getItem('decap-cms.config'));
3. GitHub OAuth App 测试
在 GitHub OAuth App 设置中:
- 查看授权历史
- 检查请求统计
- 重置 Client Secret(如果泄露)
八、总结
8.1 核心要点
- OAuth 是授权协议,不是认证协议
- 本地环境能工作是因为浏览器的特殊处理和 postMessage 机制
- 线上环境需要代理服务器来保护 Client Secret 和解决 CORS
- GitHub Pages 的限制导致默认的 Netlify Identity 无法使用
- 部署 OAuth 代理服务器是解决问题的关键
8.2 选择建议
- 生产环境:使用 Vercel OAuth 代理服务器
- 追求极致性能:使用 Cloudflare Worker
- 本地开发:使用 local_backend 模式
8.3 学习资源
最后更新时间:2026-01-30
适用场景:Astro + Decap CMS + GitHub Pages 项目