1. 免责声明
在撰写本文时,我旨在为技术爱好者提供有关如何使用 Netlify 搭建反向代理的技术指导和示例。请注意,本文中的信息和建议仅供参考,具体实施可能会因实际情况而有所不同。使用本文提供的方法和步骤时,请确保遵循当地法律法规,并充分考虑网络安全和数据保护的要求。
本文作者和发布平台对因使用本文内容而导致的任何直接或间接损失、损害或法律责任不承担任何责任。建议在实施任何技术方案之前,进行充分的测试和风险评估,并寻求专业意见,以确保符合适用的法律和行业标准。
2. 合法用途声明
请遵守相关法律法规,包括但不限于《中华人民共和国网络安全法》和《互联网信息服务管理办法》。请确保在使用 Netlify 或其他服务时:
- 合法合规:确保所搭建的反向代理不用于违反法律法规的活动,包括但不限于绕过审查、非法数据传输、或其他违法用途。
- 尊重版权和隐私:遵守版权法律,确保所代理的内容不侵犯第三方的知识产权。同时,保障用户隐私,遵循数据保护的相关要求。
- 网络安全:采取必要的安全措施,防止反向代理服务被用于恶意目的,保障系统和用户数据的安全。
- 信息透明:确保提供的服务信息清晰透明,用户可以明确知晓反向代理的用途和可能的风险。
前言 / 摘要#
在当今互联网环境下,尤其是部分国外和国内网站常常由于 DNS 污染和其他原因而无法正常访问,这成为了许多用户的一大困扰。此外,有些网站虽然仅能通过 IP 地址进行访问,却无法绑定自定义域名,这同样限制了其灵活性和可用性。为了解决这些问题,搭建一个反向代理服务器成为了一个有效的解决方案。反向代理不仅可以帮助用户绕过 DNS 污染的问题,还能让我们以自定义的域名来访问那些仅能通过 IP 地址访问的网站。
在本篇文章中,我们将利用 Netlify 这一强大而易于使用的工具来搭建反向代理。Netlify 的强大之处在于其简洁的配置过程和强大的功能,使得即使是对技术不太熟悉的用户也能轻松上手。要完成这项任务,你只需具备两个基本条件:一个 Netlify 账号和一个 GitHub 账号。接下来,我们将详细介绍如何通过这两个账户配置反向代理。
正文#
温馨提示
标题后面有 (选) 的为后台相关内容,如不需要可以跳过
第一步 准备用于搭建反代的环境#
1. 创建储存库#
创建一个用于存放 Netlify 配置文件的 GitHub 储存库
如图:
2. 创建配置文件#
新建名为 netlify.toml
的配置文件
如图:
文件内容为:
[[redirects]]
from = "/*"
to = "https://github.com/:splat"
status = 200
选择性内容#
再新建一个储存库,用于搭建管理后台 (选)
如图:
储存库中包含的文件 (选):#
│ index.html
│ package.json
│ README.md
│
└─netlify
└─functions
auth-callback.js
check-auth.js
update-redirect.js
- index.html:应用的主要前端页面,包含用户登录和配置反向代理的表单。(选)
温馨提示
请在代码中的【这里填你的反代网站,不是后台】中填入你的反代网址 (就是用存放 Netlify 配置文件的 GitHub 储存库搭建的那个)【这里填你的 GitHub clientid】中填入你的 GitHub clientid,获取方法见下文
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>反代配置</title>
<style>
body {
font-family: Arial, sans-serif;
background: linear-gradient(to bottom, #87CEFA, #FFFFFF);
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
overflow-x: hidden;
}
.container {
text-align: center;
width: 90%;
max-width: 500px;
}
h1 {
color: #333;
margin-bottom: 20px;
font-size: 1.8em;
}
form {
background: #f9f9f9;
border-radius: 15px;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease-in-out;
}
form:hover {
transform: scale(1.02);
}
label {
font-size: 1.2em;
display: block;
margin-bottom: 10px;
color: #555;
}
input[type="url"] {
width: calc(100% - 22px);
padding: 10px;
border-radius: 8px;
border: 1px solid #ddd;
margin-bottom: 20px;
font-size: 1em;
box-sizing: border-box;
}
button {
background-color: #87CEFA;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 1.1em;
transition: background-color 0.3s ease-in-out;
}
button:hover {
background-color: #00BFFF;
}
#message {
font-size: 1.2em;
color: #333;
margin-top: 20px;
}
.error-container,
.success-container {
display: none;
background: #f0f8ff;
border: 1px solid #d4edda;
border-radius: 8px;
padding: 20px;
margin-top: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
text-align: center;
}
.error-container {
border-color: #f5c6cb;
background: #f8d7da;
color: #721c24;
}
.success-container {
border-color: #c3e6cb;
background: #d4edda;
color: #155724;
}
.error-container p,
.success-container p {
margin: 0;
}
.success-container button {
margin-top: 10px;
background-color: #4CAF50;
border: none;
}
.success-container button:hover {
background-color: #45a049;
}
.tooltip {
position: relative;
display: inline-block;
cursor: pointer;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 200px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 10px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -100px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
#loginContainer {
display: none;
}
</style>
</head>
<body>
<div class="container">
<h1>反代配置器</h1>
<div id="loginContainer">
<p>请登录以访问此页面。</p>
<button id="loginButton">使用 GitHub 登录</button>
</div>
<form id="redirectForm" style="display: none;">
<div class="tooltip">
<label for="website">请输入要反代到的网址:</label>
<span class="tooltiptext">请输入完整的 URL 地址,例如 https://example.com</span>
</div>
<input type="url" id="website" name="website" required>
<button type="submit">提交</button>
</form>
<div id="errorMessage" class="error-container">
<p id="errorStatusMessage"></p>
</div>
<div id="successMessage" class="success-container">
<p id="successStatusMessage"></p>
<button id="redirectButton" style="display: none;">前往反代网站</button>
</div>
</div>
<script>
async function checkAuthentication() {
const response = await fetch('/.netlify/functions/check-auth');
const result = await response.json();
if (result.authenticated) {
document.getElementById('loginContainer').style.display = 'none';
document.getElementById('redirectForm').style.display = 'block';
} else {
document.getElementById('loginContainer').style.display = 'block';
document.getElementById('redirectForm').style.display = 'none';
}
}
document.getElementById('loginButton').addEventListener('click', () => {
window.location.href = `https://github.com/login/oauth/authorize?client_id=【这里填你的GitHub clientid】&redirect_uri=${encodeURIComponent(window.location.origin + '/.netlify/functions/auth-callback')}`;
});
document.getElementById('redirectForm').addEventListener('submit', async (event) => {
event.preventDefault();
const website = document.getElementById('website').value;
const urlPattern = /^https:\/\/[^\/]+$/;
const errorMessageElement = document.getElementById('errorStatusMessage');
const successMessageElement = document.getElementById('successStatusMessage');
const errorContainer = document.getElementById('errorMessage');
const successContainer = document.getElementById('successMessage');
const redirectButton = document.getElementById('redirectButton');
if (!urlPattern.test(website)) {
const suggestion = website.replace(/\/+$/, '');
errorMessageElement.innerText = `输入的 URL 格式不正确。请确保网址以 'https://example.com' 形式输入。你可能的正确输入是:${suggestion}`;
errorContainer.style.display = 'block';
successContainer.style.display = 'none';
return;
}
const response = await fetch('/.netlify/functions/update-redirect', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ website })
});
const result = await response.json();
if (result.message === 'Redirect updated successfully!') {
successMessageElement.innerText = '反代配置成功!请等待服务器更新,预计1分钟。';
successContainer.style.display = 'block';
errorContainer.style.display = 'none';
redirectButton.style.display = 'inline-block';
redirectButton.addEventListener('click', () => {
window.location.href = '【这里填你的反代网站,不是后台】';
});
} else {
errorMessageElement.innerText = `配置失败:${result.message}`;
errorContainer.style.display = 'block';
successContainer.style.display = 'none';
}
});
// Initialize the authentication check
checkAuthentication();
</script>
</body>
</html>
- /netlify/functions/auth-callback.js:处理 GitHub OAuth 回调的函数,设置用户的登录状态。(选)
exports.handler = async function(event, context) {
const clientId = process.env.GITHUB_CLIENT_ID;
const clientSecret = process.env.GITHUB_CLIENT_SECRET;
const allowedUsers = process.env.ALLOWED_USERS.split(',');
const jwtSecret = process.env.JWT_SECRET;
const code = event.queryStringParameters.code;
if (!code) {
return {
statusCode: 400,
body: JSON.stringify({ message: 'Missing code parameter' })
};
}
try {
const fetch = (await import('node-fetch')).default;
const jwt = (await import('jsonwebtoken')).default;
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: clientId,
client_secret: clientSecret,
code: code
})
});
const data = await response.json();
if (data.error) {
throw new Error(data.error_description);
}
const userResponse = await fetch('https://api.github.com/user', {
headers: {
Authorization: `token ${data.access_token}`
}
});
const userInfo = await userResponse.json();
if (allowedUsers.includes(userInfo.login)) {
const token = jwt.sign({ username: userInfo.login }, jwtSecret, { expiresIn: '1h' });
return {
statusCode: 302,
headers: {
'Set-Cookie': `token=${token}; Path=/; HttpOnly`,
'Location': '/'
},
body: ''
};
} else {
return {
statusCode: 403,
body: JSON.stringify({ message: 'Unauthorized' })
};
}
} catch (error) {
return {
statusCode: 500,
body: `Error: ${error.message}`
};
}
};
- /netlify/functions/check-auth.js:检查用户是否已登录的函数。(选)
exports.handler = async function(event, context) {
const jwtSecret = process.env.JWT_SECRET;
const cookieHeader = event.headers.cookie || '';
const cookies = Object.fromEntries(cookieHeader.split('; ').map(cookie => cookie.split('=')));
const token = cookies.token;
try {
const jwt = (await import('jsonwebtoken')).default;
jwt.verify(token, jwtSecret);
return {
statusCode: 200,
body: JSON.stringify({ authenticated: true })
};
} catch (err) {
return {
statusCode: 403,
body: JSON.stringify({ authenticated: false })
};
}
};
- /netlify/functions/update-redirect.js:处理反向代理配置的函数。(选)
温馨提示
请在代码中的【这里填入你的 GitHub 用户名】【用于存放 Netlify 配置文件的 GitHub 储存库名称】中分别填入你的 GitHub 用户名和用于存放 Netlify 配置文件的 GitHub 储存库名称,获取方法见下文
exports.handler
const jwtSecret = process.env.JWT_SECRET;
const allowedUsers = process.env.ALLOWED_USERS.split(',');
const cookieHeader = event.headers.cookie || '';
const cookies = Object.fromEntries(cookieHeader.split('; ').map(cookie => cookie.split('=')));
const token = cookies.token;
let decodedToken;
try {
const jwt = (await import('jsonwebtoken')).default;
decodedToken = jwt.verify(token, jwtSecret);
} catch (err) {
return {
statusCode: 403,
body: JSON.stringify({ message: 'Unauthorized' })
};
}
if (!allowedUsers.includes(decodedToken.username)) {
return {
statusCode: 403,
body: JSON.stringify({ message: 'Unauthorized' })
};
}
const { Octokit } = await import('@octokit/rest');
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN
});
const { website } = JSON.parse(event.body);
if (!website) {
return {
statusCode: 400,
body: JSON.stringify({ message: 'No website provided' })
};
}
try {
const fetch = (await import('node-fetch')).default;
const { data: fileData } = await octokit.repos.getContent({
owner: '【这里填入你的GitHub用户名】',
repo: '【用于存放Netlify配置文件的GitHub储存库名称】',
path: 'netlify.toml'
});
const content = Buffer.from(fileData.content, 'base64').toString();
const newContent = content.replace(/to\s*=\s*"[^"]+"/, `to = "${website}/:splat"`);
await octokit.repos.createOrUpdateFileContents({
owner: '【这里填入你的GitHub用户名】',
repo: '【用于存放Netlify配置文件的GitHub储存库名称】',
path: 'netlify.toml',
message: 'Update redirect URL',
content: Buffer.from(newContent).toString('base64'),
sha: fileData.sha
});
return {
statusCode: 200,
body: JSON.stringify({ message: 'Redirect updated successfully!' })
};
} catch (error) {
console.error(error);
return {
statusCode: 500,
body: JSON.stringify({ message: 'Failed to update redirect' })
};
}
};
- package.json:存放依赖的文件。(选)
{ "name": "Rcs, "version": "1.0.0", "description": "A reverse proxy configurator with GitHub authentication deployed on Netlify.", "main": "index.js", "scripts": { "start": "netlify dev", "build": "netlify build" }, "dependencies": { "@octokit/rest": "^19.0.7", "jsonwebtoken": "^9.0.2", "node-fetch": "^3.3.2" }, "devDependencies": { "netlify-cli": "^15.0.0" }, "engines": { "node": ">=16.x" }, "author": "j23", "license": "MIT" }
第二步 配置 Netlify#
1. 创建一个站点#
如图:
- 选择你的用于存放 Netlify 配置文件的 GitHub 储存库
2. 配置自定义域名#
自此所有基本操作已经完成,如果不需要配置后台,你只需要修改 Netlify 配置文件中的链接,就可以实现更换需要代理的网站。
修改内容如下 (注意!,网址格式必须为https://example.com/,不能删去 https:// 也不能删去最后的 /):
[[redirects]]
from = "/*"
to = "【修改这里】:splat"
status = 200
选择性内容#
一.GitHub 登录#
使用 GitHub 进行 OAuth 登录,确保只有授权用户可以访问配置功能。
第一步:#
打开下面网址,并填好配置https://github.com/settings/applications/new
第二步:#
获取 Client ID,并生成 Client secrets,后面要用
2.
警告!
请妥善保管好你的 Client secrets!
二。配置用于修改储存库的密钥#
-
打开下面的网址https://github.com/settings/tokens
-
填入相关内容,选择不过期,勾选储存库
- 获取到密钥
警告!
请妥善保管好你的密钥
三。配置 Netlify#
第一步:创建站点#
如图:
- 选择你的后台储存库
- 填入环境变量
ALLOWED_USERS:你授权使用后台的GitHub用户名,用半角逗号隔开,如果只有一个用户,则无需逗号。如:用户名1,用户名2 GITHUB_CLIENT_ID:你的CLIENT_ID GITHUB_CLIENT_SECRET:你的CLIENT_SECRET GITHUB_TOKEN:你的GitHub账号密钥 JWT_SECRET:一个随机的不限位数的字符串,如wfhehfihwjdwefnabwaieu72dub3uigfvqty2UGQWADIWGADIY3UGF378
如果这一步漏掉了,可以在下面页面找到并补充
第二步。配置自定义域名#
结尾#
非常感谢你能看到这里,如有什么问题欢迎在评论区中提出!如果可以的话,请订阅我的网站:https://jdjwzx233.cn/Subscribe