CF的短链添加密码访问
Cloudflare Workers 短链服务添加密码保护完整指南
概述
本文档介绍如何为 Cloudflare Workers 上的短链服务(URL Shortener)添加密码访问保护。实现以下功能:
- 访问首页(根路径)需要密码
- 访问具体短链(如 /abc123)不需要密码
- 创建短链的 API 需要密码(防止滥用)
需要修改的文件
只需修改 1 个文件:Worker 的主入口文件(通常是 index.js 或 worker.js)
完整代码
只添加账号密码访问
// 1. 定义默认跳转的外部 URL(当访问根目录或短码不存在时)
const DEFAULT_TARGET_URL = 'https://www.example.com';
// 2. 定义创建接口的路径
const CREATE_PATH = '/api/create';
// 统一的 JSON 响应工具
function jsonResponse(data, status = 200) {
return new Response(JSON.stringify(data), {
status: status,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
// 统一的重定向工具
function redirectResponse(url) {
return new Response(null, {
status: 302,
headers: { 'Location': url },
});
}
// ========== 添加认证函数 ==========
// 方案一:仅密码验证
function requireAuth(request, password) {
const auth = request.headers.get('Authorization');
if (!auth || !auth.startsWith('Basic ')) {
return { authorized: false };
}
try {
const base64 = auth.slice(6);
const credentials = atob(base64);
const [, inputPassword] = credentials.split(':');
const encoder = new TextEncoder();
const p1 = encoder.encode(inputPassword);
const p2 = encoder.encode(password || '');
if (p1.length !== p2.length) return { authorized: false };
let valid = true;
for (let i = 0; i < p1.length; i++) {
if (p1[i] !== p2[i]) valid = false;
}
return { authorized: valid };
} catch {
return { authorized: false };
}
}
// 方案二:账号+密码验证(推荐)
function requireAuthFull(request, env) {
const auth = request.headers.get('Authorization');
if (!auth || !auth.startsWith('Basic ')) {
return { authorized: false };
}
try {
const base64 = auth.slice(6);
const credentials = atob(base64);
const [inputUsername, inputPassword] = credentials.split(':');
const validUsername = env.SHORTLINK_USERNAME || 'admin';
const validPassword = env.SHORTLINK_PASSWORD || '';
const usernameMatch = secureCompare(inputUsername, validUsername);
const passwordMatch = secureCompare(inputPassword, validPassword);
return { authorized: usernameMatch && passwordMatch };
} catch {
return { authorized: false };
}
}
// 安全字符串比较
function secureCompare(a, b) {
if (!a || !b) return a === b;
const encoder = new TextEncoder();
const aBuf = encoder.encode(a);
const bBuf = encoder.encode(b);
if (aBuf.length !== bBuf.length) return false;
let result = 0;
for (let i = 0; i < aBuf.length; i++) {
result |= aBuf[i] ^ bBuf[i];
}
return result === 0;
}
// =================================
// 处理短链接创建/更新
async function handleCreate(request, env) {
try {
const body = await request.json();
const { shortcode, url } = body;
if (!shortcode || !url) {
return jsonResponse({ success: false, message: 'Missing required fields: shortcode and url' }, 400);
}
try {
new URL(url);
} catch (e) {
return jsonResponse({ success: false, message: 'Invalid URL format' }, 400);
}
const normalizedShortcode = shortcode.toLowerCase().trim();
if (normalizedShortcode === '' || normalizedShortcode.startsWith('api')) {
return jsonResponse({ success: false, message: 'Invalid or reserved shortcode' }, 400);
}
await env.LINKS.put(normalizedShortcode, url);
const shortUrl = `${new URL(request.url).origin}/${normalizedShortcode}`;
return jsonResponse({
success: true,
message: 'Short link created successfully',
shortcode: normalizedShortcode,
longUrl: url,
shortUrl: shortUrl,
}, 200);
} catch (e) {
return jsonResponse({ success: false, message: `Error processing request: ${e.message}` }, 500);
}
}
// 核心逻辑调度器(添加了认证)
async function handleRequest(request, env) {
const url = new URL(request.url);
const path = url.pathname.slice(1);
// ========== 添加 Basic Auth 认证 ==========
// 选择使用方案一或方案二,二选一即可
// 方案一:仅密码验证(需要设置环境变量 SHORTLINK_PASSWORD)
const authResult = requireAuth(request, env.SHORTLINK_PASSWORD);
// 方案二:账号+密码验证(需要设置环境变量 SHORTLINK_USERNAME 和 SHORTLINK_PASSWORD)
// const authResult = requireAuthFull(request, env);
if (!authResult.authorized) {
return new Response("Unauthorized", {
status: 401,
headers: {
'WWW-Authenticate': 'Basic realm="ShortLink Service"',
'Access-Control-Allow-Origin': '*'
}
});
}
// ==========================================
// 1. 拦截创建短链接的 POST 请求
if (request.method === 'POST' && url.pathname === CREATE_PATH) {
return handleCreate(request, env);
}
// 2. 拦截根路径 GET 请求 (/)
if (request.method === 'GET' && path === '') {
return redirectResponse(DEFAULT_TARGET_URL);
}
// 3. 处理短链接重定向逻辑
if (request.method === 'GET') {
const targetUrl = await env.LINKS.get(path);
if (targetUrl) {
return redirectResponse(targetUrl);
} else {
return redirectResponse(DEFAULT_TARGET_URL);
}
}
return new Response('Method Not Allowed', { status: 405 });
}
// Cloudflare Workers 模块化标准入口
export default {
async fetch(request, env, ctx) {
return handleRequest(request, env);
}
};制作短链需要密码访问,短链不需要密码访问
// 1. 定义默认跳转的外部 URL(当访问根目录或短码不存在时)
const DEFAULT_TARGET_URL = 'https://www.example.com';
// 2. 定义创建接口的路径
const CREATE_PATH = '/api/create';
// 统一的 JSON 响应工具
function jsonResponse(data, status = 200) {
return new Response(JSON.stringify(data), {
status: status,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
// 统一的重定向工具
function redirectResponse(url) {
return new Response(null, {
status: 302,
headers: { 'Location': url },
});
}
// ========== 认证函数 ==========
// 方案一:仅密码验证
function requireAuth(request, password) {
const auth = request.headers.get('Authorization');
if (!auth || !auth.startsWith('Basic ')) {
return { authorized: false };
}
try {
const base64 = auth.slice(6);
const credentials = atob(base64);
const [, inputPassword] = credentials.split(':');
const encoder = new TextEncoder();
const p1 = encoder.encode(inputPassword);
const p2 = encoder.encode(password || '');
if (p1.length !== p2.length) return { authorized: false };
let valid = true;
for (let i = 0; i < p1.length; i++) {
if (p1[i] !== p2[i]) valid = false;
}
return { authorized: valid };
} catch {
return { authorized: false };
}
}
// 安全字符串比较函数
function secureCompare(a, b) {
if (!a || !b) return a === b;
const encoder = new TextEncoder();
const aBuf = encoder.encode(a);
const bBuf = encoder.encode(b);
if (aBuf.length !== bBuf.length) return false;
let result = 0;
for (let i = 0; i < aBuf.length; i++) {
result |= aBuf[i] ^ bBuf[i];
}
return result === 0;
}
// 方案二:账号+密码验证(可选)
function requireAuthFull(request, env) {
const auth = request.headers.get('Authorization');
if (!auth || !auth.startsWith('Basic ')) {
return { authorized: false };
}
try {
const base64 = auth.slice(6);
const credentials = atob(base64);
const [inputUsername, inputPassword] = credentials.split(':');
const validUsername = env.SHORTLINK_USERNAME || 'admin';
const validPassword = env.SHORTLINK_PASSWORD || '';
const usernameMatch = secureCompare(inputUsername, validUsername);
const passwordMatch = secureCompare(inputPassword, validPassword);
return { authorized: usernameMatch && passwordMatch };
} catch {
return { authorized: false };
}
}
// =================================
// 处理短链接创建/更新
async function handleCreate(request, env) {
try {
const body = await request.json();
const { shortcode, url } = body;
if (!shortcode || !url) {
return jsonResponse({ success: false, message: 'Missing required fields: shortcode and url' }, 400);
}
try {
new URL(url);
} catch (e) {
return jsonResponse({ success: false, message: 'Invalid URL format' }, 400);
}
const normalizedShortcode = shortcode.toLowerCase().trim();
if (normalizedShortcode === '' || normalizedShortcode.startsWith('api')) {
return jsonResponse({ success: false, message: 'Invalid or reserved shortcode' }, 400);
}
await env.LINKS.put(normalizedShortcode, url);
const shortUrl = `${new URL(request.url).origin}/${normalizedShortcode}`;
return jsonResponse({
success: true,
message: 'Short link created successfully',
shortcode: normalizedShortcode,
longUrl: url,
shortUrl: shortUrl,
}, 200);
} catch (e) {
return jsonResponse({ success: false, message: `Error processing request: ${e.message}` }, 500);
}
}
// 核心逻辑调度器
async function handleRequest(request, env) {
const url = new URL(request.url);
const path = url.pathname.slice(1); // 获取短码,例如 'abc123'
const requestPath = url.pathname; // 获取完整路径,例如 '/' 或 '/abc123'
// 情况1:访问首页 (/) - 需要密码
if (request.method === 'GET' && requestPath === '/') {
// 默认使用仅密码验证
const authResult = requireAuth(request, env.SHORTLINK_PASSWORD);
// 如需使用账号+密码验证,注释上一行,取消下一行注释
// const authResult = requireAuthFull(request, env);
if (!authResult.authorized) {
return new Response("Unauthorized", {
status: 401,
headers: {
'WWW-Authenticate': 'Basic realm="ShortLink Service"',
'Access-Control-Allow-Origin': '*'
}
});
}
// 认证成功,重定向到默认页面
return redirectResponse(DEFAULT_TARGET_URL);
}
// 情况2:创建短链接 API - 需要密码(防止被滥用)
if (request.method === 'POST' && url.pathname === CREATE_PATH) {
const authResult = requireAuth(request, env.SHORTLINK_PASSWORD);
// 如需使用账号+密码验证,注释上一行,取消下一行注释
// const authResult = requireAuthFull(request, env);
if (!authResult.authorized) {
return new Response("Unauthorized", {
status: 401,
headers: {
'WWW-Authenticate': 'Basic realm="ShortLink API"',
'Access-Control-Allow-Origin': '*'
}
});
}
return handleCreate(request, env);
}
// 情况3:访问具体短链 (例如 /abc123) - 不需要密码
if (request.method === 'GET') {
const targetUrl = await env.LINKS.get(path);
if (targetUrl) {
// 找到对应的长链接,直接重定向,无需密码
return redirectResponse(targetUrl);
} else {
// 短码不存在,重定向到首页(首页需要密码)
return redirectResponse(DEFAULT_TARGET_URL);
}
}
// 其他请求方法不允许
return new Response('Method Not Allowed', { status: 405 });
}
// Cloudflare Workers 模块化标准入口
export default {
async fetch(request, env, ctx) {
return handleRequest(request, env);
}
};代码修改说明
认证添加位置
在 handleRequest 函数中,需要添加认证的位置有两处:
- 首页访问处理:在
if (request.method === 'GET' && requestPath === '/')代码块内,在return redirectResponse之前添加认证 - API 创建处理:在
if (request.method === 'POST' && url.pathname === CREATE_PATH)代码块内,在return handleCreate之前添加认证
如何切换认证方案
使用仅密码验证(默认)
const authResult = requireAuth(request, env.SHORTLINK_PASSWORD);切换为账号+密码验证
将:
const authResult = requireAuth(request, env.SHORTLINK_PASSWORD);改为:
const authResult = requireAuthFull(request, env);环境变量设置
在 Cloudflare 仪表板中设置:
| 方案 | 变量名 | 值 | 类型 |
|---|---|---|---|
| 仅密码 | SHORTLINK_PASSWORD | 你的密码 | 加密文本 |
| 账号+密码 | SHORTLINK_USERNAME | 你的用户名 | 加密文本 |
| 账号+密码 | SHORTLINK_PASSWORD | 你的密码 | 加密文本 |
设置步骤:
- 登录 Cloudflare Dashboard
- 进入 Workers 和 Pages → 你的 Worker
- 点击设置 → 变量
- 添加加密文本环境变量
访问效果
| 访问路径 | 是否需要密码 | 说明 |
|---|---|---|
| https://你的域名/ | 需要 | 首页,输入密码后跳转到 DEFAULT_TARGET_URL |
| https://你的域名/abc123 | 不需要 | 具体短链,直接重定向到原链接 |
| POST https://你的域名/api/create | 需要 | 创建短链 API,防止他人滥用 |
API 调用示例
使用 curl 调用创建短链接接口:
# 仅密码验证
curl -X POST https://your-worker.dev/api/create \
-H "Authorization: Basic $(echo -n ':mypassword' | base64)" \
-H "Content-Type: application/json" \
-d '{"shortcode":"test","url":"https://example.com"}'
# 账号+密码验证
curl -X POST https://your-worker.dev/api/create \
-H "Authorization: Basic $(echo -n 'admin:mypassword' | base64)" \
-H "Content-Type: application/json" \
-d '{"shortcode":"test","url":"https://example.com"}'部署命令
npx wrangler deploy常见问题
Q: 修改代码后如何确认认证生效?
部署后,用浏览器无痕模式访问你的域名,应该会弹出登录框。
Q: 为什么具体短链不需要密码?
因为代码中只对 requestPath === '/' 的请求进行认证,其他路径直接放行。
Q: 如何保护创建 API 但公开首页?
只需删除首页认证的代码块,保留 API 认证即可。
Q: 如何修改默认跳转地址?
修改代码开头的 DEFAULT_TARGET_URL 变量值。
方案对比
| 特性 | 方案一(仅密码) | 方案二(账号+密码) |
|---|---|---|
| 需要修改的文件 | 1 个 | 1 个 |
| 需要设置的变量 | 1 个 | 2 个 |
| 用户名验证 | 无 | 有 |
| 安全性 | 中 | 高 |
| 实现复杂度 | 简单 | 简单 |