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