前端后端检测IP 检测VPN 检测代理 的解决方案
本案例实现方案有以下两种
- 前端WebRTC真实IP与ipify获取的ip进行对比(前端方案)
- ipify获取的ip给第三方服务检测(第三方检测方案)
推荐个人项目
- 这是一个
国际化全栈中后台解决方案,支持16种语言/三种主题切换 - 前端:
vue3.5+typescript+vite5+pinia+element-plus+unocss+sass - 后端
nodejs+express+MySQL/PostgreSQL+redis的管理后台项目 - 预览:网络需要绕过大陆 kilyicms.vercel.app
github代码仓库 kilyicms- 创作不易希望点个
star⭐⭐⭐
🍊本案例效果展示页面:点击链接
🍊案例使用vue3.5+typescript+vite5+pinia+element-plus+unocss+sass
方案一:前端 WebRTC 真实 IP 与 ipify 获取的 IP 进行对比
该方案基于前端技术,通过 WebRTC 获取用户的真实 IP 地址,然后与 ipify 获取的公共 IP 地址进行对比,判断是否存在代理或 VPN 使用的可能性。
1.1. WebRTC 的工作原理
WebRTC(Web Real-Time Communication)是一种允许浏览器进行实时语音、视频和数据传输的技术。它具有通过 P2P(点对点)网络连接直接与其他设备通信的能力,因此能获取到本地网络中的真实 IP 地址。此技术的一个特点是,当浏览器与其他用户建立连接时,会暴露本地的局域网 IP 地址,因此能够检测到代理或 VPN 的使用情况。
1.2. ipify 获取的公共 IP 地址
ipify 是一个简单的 API 服务,用于获取用户的公共 IP 地址。在浏览器中,我们可以通过向 ipify 发送请求来获取外部网络所识别的 IP 地址。由于 VPN 或代理服务可能会改变用户的公共 IP 地址,我们可以将其与通过 WebRTC 获取的真实 IP 地址进行对比,以判断是否存在 IP 地址欺骗或代理。
1.3. 使用 Vue 组件实现
在代码中,我们通过 Vue 组件 <WebrtcIp> 来获取 WebRTC 真实 IP,并通过 <Proxycheck> 组件获取 ipify 获取的 IP 地址,再通过对比逻辑判断是否存在 VPN 或代理。
<template>
<div>
<h2>用户IP 和 VPN/代理检测</h2>
<el-card shadow="hover">
<div flex items-center justify-between>
<h3 class="text-blue-500">WebRTC 和 ipify 对比判断案例 🔽</h3>
<el-button type="primary" @click="reloadComponent" icon="Refresh">
重新检测
</el-button>
</div>
<div class="flex items-center gap-4">
<WebrtcIp :key="componentKey"></WebrtcIp>
</div>
</el-card>
<el-card shadow="hover">
<div flex items-center justify-between>
<h3 class="text-blue-500">
ipify 检测的IP通过第三方服务判断(eg: proxycheck.io) 🔽
</h3>
<el-button
type="primary"
@click="reloadComponentProxycheck"
icon="Refresh"
>
重新检测
</el-button>
</div>
<div class="flex items-center gap-4">
<Proxycheck :key="componentKeyProxycheck"></Proxycheck>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
// 前端WebRTC真实IP与ipify获取的ip进行对比 组件
import WebrtcIp from "@/components/WebrtcIp/WebrtcIp.vue";
// ipify获取的ip给第三方服务检测 组件
import Proxycheck from "@/components/WebrtcIp/Proxycheck.vue";
// 用于刷新组件的 key
const componentKey = ref(0);
const componentKeyProxycheck = ref(0);
// 重新加载组件
const reloadComponent = () => {
componentKey.value++;
};
const reloadComponentProxycheck = () => {
componentKeyProxycheck.value++;
};
</script>
<style scoped>
:deep(.el-card) {
margin: 5px;
display: flex;
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
.el-card__body {
width: 100%;
}
}
</style>
在 Vue 组件中,我们通过 reloadComponent 和 reloadComponentProxycheck 方法来刷新组件,重新检测 IP 地址。通过这种方式,用户可以实时查看 WebRTC 真实 IP 与 ipify 获取的公共 IP 地址之间的对比结果。
WebrtcIp 组件内容
- 注意点是 ipify 获取 IP地址的接口不要后端来代理,前端直接发起GET请求才是当前用户客户端IP
<template>
<div>
<p>
该演示会秘密向 STUN 服务器发出请求,这些服务器会记录你的请求。
这些请求不会显示在开发者控制台中,而且无法被浏览器插件(AdBlock、Ghostery
等)阻止。
</p>
<el-row>
<el-col :span="24">
<h4>
你的 WebRTC 公网 IP 地址是: 
<span>{{ webrtcIp }}</span>
</h4>
</el-col>
<el-col :span="24">
<h4>
通过 ipify 获取的公网 IP 地址是: 
<span>{{ ipifyIp }}</span>
</h4>
</el-col>
</el-row>
<p :class="comparisonResultClass">{{ comparisonResult.message }}</p>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import { ElMessage } from "element-plus";
const ipifyIp = ref<string>("");
const webrtcIp = ref<string>("");
const comparisonResult = ref<{ key: string; message: string }>({
key: "",
message: "",
});
const comparisonResultClass = computed(() =>
comparisonResult.value.key === "MATCH"
? "text-sky-500 font-bold text-xl"
: "text-red-500 font-bold text-xl",
);
// 获取 WebRTC 的 IP 地址
const getWebrtcIP = (): Promise<string> =>
new Promise((resolve) => {
const ipDups: Record<string, boolean> = {};
const RTCPeerConnection =
window.RTCPeerConnection ||
(window as any).mozRTCPeerConnection ||
(window as any).webkitRTCPeerConnection;
if (!RTCPeerConnection) {
console.error("WebRTC is not supported by your browser.");
return;
}
const servers = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };
const pc = new RTCPeerConnection(servers);
pc.onicecandidate = (ice) => {
if (ice.candidate) {
const ipMatch =
/([0-9]{1,3}(.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/.exec(
ice.candidate.candidate,
);
if (ipMatch && !ipDups[ipMatch[1]]) {
ipDups[ipMatch[1]] = true;
resolve(ipMatch[1]);
}
}
};
pc.createDataChannel("");
pc.createOffer()
.then((offer) => pc.setLocalDescription(offer))
.catch((err) => console.error("Error creating WebRTC offer", err));
setTimeout(() => {
pc.localDescription?.sdp.split("\n").forEach((line) => {
if (line.startsWith("a=candidate:")) {
const ipMatch =
/([0-9]{1,3}(.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/.exec(
line,
);
if (ipMatch && !ipDups[ipMatch[1]]) {
ipDups[ipMatch[1]] = true;
resolve(ipMatch[1]);
}
}
});
}, 2000);
});
// 获取 ipify 的 IP 地址
const getIpifyIP = async () => {
try {
const response = await fetch("https://api.ipify.org?format=json");
const data = await response.json();
ipifyIp.value = data.ip;
} catch (error) {
ElMessage.error("获取 ipify IP 地址失败,请检查网络连接!");
console.error("Error fetching ipify:", error);
}
};
// 比较 IP 地址
const compareIPs = async () => {
await getIpifyIP();
const webrtc = await getWebrtcIP();
webrtcIp.value = webrtc;
if (webrtc === ipifyIp.value) {
comparisonResult.value = {
key: "MATCH",
message:
"🟢检测结果:WebRTC 和 ipify 的 IP 地址相同,你没有使用 VPN/代理✅",
};
} else {
comparisonResult.value = {
key: "MISMATCH",
message:
"🔴检测结果:WebRTC 和 ipify 的 IP 地址不同,你可能正在使用 VPN/代理❌",
};
}
};
onMounted(compareIPs);
</script>
<style scoped>
.text-sky-500 {
color: skyblue;
}
.text-red-500 {
color: red;
}
.font-bold {
font-weight: bold;
}
.text-xl {
font-size: 1.25rem; /* 20px */
}
h4 {
line-height: 1;
}
</style>
方案一 预览图
1.正常情况(没有开代理/VPN):
2.异常情况 (开了代理/VPN ---- 虚拟 新加坡)
方案二:使用 ipify 获取的 IP 并通过第三方服务检测
在该方案中,我们利用 ipify 获取到的公共 IP 地址,再通过第三方服务(如 proxycheck.io)进行进一步的检测,判断该 IP 是否属于 VPN 或代理服务器。
2.1. 第三方服务的优势
使用第三方服务进行 VPN 或代理检测,通常能够获得更准确的结果。这些服务通过检查 IP 是否属于已知的 VPN 或代理服务器池,能够有效识别隐藏用户真实 IP 的行为。
2.2. 示例:Proxycheck.io 服务
Proxycheck.io 是一个提供代理检测服务的平台,它能够识别公共 IP 地址是否属于已知的 VPN、代理服务器或数据中心 IP。通过调用其 API,我们可以向其提供公共 IP 地址,进而判断是否存在代理。
2.3. 使用 Vue 组件调用第三方检测服务
在此方案中,我们通过 Proxycheck 组件来调用代理检测服务。每当用户点击刷新按钮时,组件将通过 ipify 获取当前的公共 IP 地址,并将其提交到 proxycheck.io 服务进行检测。最终,检测结果会返回并展示给用户。
Proxycheck 组件内容为:
- 注意点是 ipify 获取 IP地址的接口不要后端来代理,前端直接发起GET请求才是当前用户客户端IP
<template>
<div>
<h2 class="text-lg font-bold mb-4">代理/VPN 检测结果</h2>
<div v-if="error" class="text-red-500">
<p>{{ error }}</p>
</div>
<div v-else-if="result" class="space-y-2">
<p>
<strong>ipify 检测当前IP 地址:</strong> {{ ip }}
</p>
<p>
<strong>proxycheck.io检测当前IP 范围:</strong> {{ result.range }}
</p>
<p>
<strong>代理:</strong> {{ result.proxy }}
</p>
<p>
<strong>类型:</strong> {{ result.type }}
</p>
<p>
<strong>提供商:</strong> {{ result.provider }}
</p>
<p>
<strong>国家:</strong> {{ result.country }} ({{ result.isocode }})
</p>
<p>
<strong>城市:</strong> {{ result.city }}
</p>
<p>
<strong>时区:</strong> {{ result.timezone }}
</p>
<p>
<strong>经纬度:</strong> {{ result.latitude }}, {{ result.longitude }}
</p>
</div>
<div v-else>
<p>数据加载中,请稍后...</p>
</div>
<p :class="comparisonResultClass">{{ comparisonResult.message }}</p>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, computed } from "vue";
import { getProxycheck } from "@/service/ip-detection";
const ip = ref<string | null>(null);
const result = ref<any | null>(null);
const error = ref<string | null>(null);
const comparisonResult = ref<{ key: string; message: string }>({
key: "",
message: "",
});
const comparisonResultClass = computed(() =>
comparisonResult.value.key === "MATCH"
? "text-sky-500 font-bold text-xl"
: "text-red-500 font-bold text-xl",
);
// 获取当前 IP 地址
const getCurrentIP = async (): Promise<string> => {
try {
const response = await fetch("https://api64.ipify.org?format=json");
if (response.ok) {
const data = await response.json();
return data.ip;
} else {
throw new Error("获取 IP 地址失败。");
}
} catch (error) {
console.error(error);
throw new Error("获取 IP 地址失败。");
}
};
// 调用 proxycheck.io 检测代理或 VPN
const checkIP = async (ip: string): Promise<any> => {
try {
const response = await getProxycheck(ip);
if (response.status === 200) {
return response;
} else {
throw new Error("检测代理/VPN 失败。");
}
} catch (error) {
console.error(error);
throw new Error("检测代理/VPN 失败。");
}
};
// 页面加载时执行检测逻辑
onMounted(async () => {
try {
ip.value = await getCurrentIP();
const detectionResult = await checkIP(ip.value);
result.value = detectionResult;
if (detectionResult.proxy === "no") {
comparisonResult.value = {
key: "MATCH",
message: "🟢 通过proxycheck.io检测,你没有使用 VPN/代理 ✅",
};
} else {
comparisonResult.value = {
key: "MISMATCH",
message: "🔴 通过proxycheck.io检测,你可能正在使用 VPN/代理 ❌",
};
}
} catch (err: any) {
console.error("发生错误:", err.message);
error.value = err.message || "检测失败,请稍后重试。";
}
});
</script>
ip-detection.ts 中内容为:
import request from "@/utils/request";
// 获取代理检测结果===第三方服务--proxycheck.io
export const getProxycheck = (ip: string) => {
return request({
url: "/proxycheck",
method: "GET",
params: { ip },
});
};
后端代理
- 这里使用Nodejs+express 做了一个proxycheck.io的代理服务
const express = require('express');
const axios = require("axios");
const router = express.Router();
// proxycheck.io代理接口
router.get("/", async (req, res) => {
const { ip } = req.query; // 从查询参数中获取 IP 地址
// const apiKey = "你的_proxycheck_api_key"; // 替换为你的 API 密钥
if (!ip) {
return res.status(400).json({ error: "IP 地址是必需的" });
}
try {
// 请求 proxycheck.io API
const response = await axios.get(
`https://proxycheck.io/v2/${ip}?vpn=1&asn=1`
);
// 提取所需字段
const data = response.data[ip];
if (data) {
const filteredData = {
...data,
status: 200,
};
return res.json(filteredData);
} else {
return res.status(404).json({ error: "未找到相关数据" });
}
} catch (error) {
console.error("代理检查失败:", error.message);
res.status(500).json({ error: "无法获取代理检查结果" });
}
});
module.exports = router;
方案二 预览图
1.正常情况(没有开代理/VPN):
2.异常情况 (开了代理/VPN ---- 虚拟 新加坡)
总结
通过这两种方案,我们可以有效地检测用户是否使用了 VPN 或代理服务。
- 方案一:通过 WebRTC 获取的真实 IP 和 ipify 获取的公共 IP 地址进行对比,适用于无需依赖第三方服务的场景。
- 方案二:通过 ipify 获取公共 IP 地址后,使用第三方服务(如 proxycheck.io)进行更精准的检测,适用于需要更高精度的检测场景。
- 本案例效果展示页面:点击链接
这两种方案可以根据实际需求选择使用,帮助开发者提高前端应用的安全性,识别和防止恶意用户或爬虫的访问。