1. 免责声明
在撰写本文时,我旨在为技术爱好者提供有关如何使用 Netlify 搭建反向代理的技术指导和示例。请注意,本文中的信息和建议仅供参考,具体实施可能会因实际情况而有所不同。使用本文提供的方法和步骤时,请确保遵循当地法律法规,并充分考虑网络安全和数据保护的要求。
本文作者和发布平台对因使用本文内容而导致的任何直接或间接损失、损害或法律责任不承担任何责任。建议在实施任何技术方案之前,进行充分的测试和风险评估,并寻求专业意见,以确保符合适用的法律和行业标准。
2. 合法用途声明
请遵守相关法律法规,包括但不限于《中华人民共和国网络安全法》和《互联网信息服务管理办法》。请确保在使用 Netlify 或其他服务时:
合法合规 :确保所搭建的反向代理不用于违反法律法规的活动,包括但不限于绕过审查、非法数据传输、或其他违法用途。尊重版权和隐私 :遵守版权法律,确保所代理的内容不侵犯第三方的知识产权。同时,保障用户隐私,遵循数据保护的相关要求。网络安全 :采取必要的安全措施,防止反向代理服务被用于恶意目的,保障系统和用户数据的安全。信息透明 :确保提供的服务信息清晰透明,用户可以明确知晓反向代理的用途和可能的风险。# 前言 / 摘要在当今互联网环境下,尤其是部分国外和国内网站常常由于 DNS 污染和其他原因而无法正常访问,这成为了许多用户的一大困扰。此外,有些网站虽然仅能通过 IP 地址进行访问,却无法绑定自定义域名,这同样限制了其灵活性和可用性。为了解决这些问题,搭建一个反向代理服务器成为了一个有效的解决方案。反向代理不仅可以帮助用户绕过 DNS 污染的问题,还能让我们以自定义的域名来访问那些仅能通过 IP 地址访问的网站。
在本篇文章中,我们将利用 Netlify 这一强大而易于使用的工具来搭建反向代理。Netlify 的强大之处在于其简洁的配置过程和强大的功能,使得即使是对技术不太熟悉的用户也能轻松上手。要完成这项任务,你只需具备两个基本条件:一个 Netlify 账号和一个 GitHub 账号。接下来,我们将详细介绍如何通过这两个账户配置反向代理。
# 正文温馨提示 标题后面有 (选) 的为后台相关内容,如不需要可以跳过
# 第一步 准备用于搭建反代的环境# 1. 创建储存库创建一个用于存放 Netlify 配置文件的 GitHub 储存库
如图:
# 2. 创建配置文件新建名为 netlify.toml
的配置文件
如图:
文件内容为:
1 2 3 4 [[redirects]] from = "/*" to = "https://github.com/:splat" status = 200
# 选择性内容再新建一个储存库,用于搭建管理后台 (选)
如图:
# 储存库中包含的文件 (选):
1 2 3 4 5 6 7 8 9 │ 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,获取方法见下文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 <!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' ; } }); checkAuthentication (); </script > </body > </html >
/netlify/functions/auth-callback.js :处理 GitHub OAuth 回调的函数,设置用户的登录状态。(选)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 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 :检查用户是否已登录的函数。(选)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 储存库名称,获取方法见下文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 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 :存放依赖的文件。(选)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 { "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:// 也不能删去最后的 /):
1 2 3 4 [[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# 第一步:创建站点如图:
选择你的后台储存库
填入环境变量1 2 3 4 5 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