基于 IP 位置的前端访问控制方案:页面在加载时请求 https://my.ipin.io/info 获取访客的国家代码(country)。当识别到 country 为 TW(或你配置的其他地区)时,页面不再继续提供正常内容,而是直接在前端终止访问流程。
很多“按地区限制访问”的需求,其实不是为了做强安全(防黑客那种),而是更常见的前端场景:
- 某些国家/地区暂不提供服务(合规、版权、支付、物流等原因)
- 想在用户进入页面时先做一次地区判断,再决定是否展示内容/按钮/下载链接
- 运营活动只面向特定地区,其他地区需要直接提示“不在服务范围”或“不可用”
你这套方案特别适合前端:浏览器直接请求 GET https://my.ipin.io/info,接口会按“发起请求的 IP”返回地理信息,例如:
{"ip":"185.220.236.7","country":"TW","region":"Taiwan","city":"Taipei"}
然后只要判断 `country === "TW"` 就执行“禁止访问”的动作(例如:显示禁止访问提示、输出 404 文案、或用你自己的错误页结构覆盖原页面)。这里的关键点是:我们不讨论“跳转”,而是把访问结果明确呈现为错误或拒绝访问,让用户看到的就是“不可访问”的终态。
1)逻辑与工作原理(2 段)
第一段:流程
用户打开网页 → 浏览器立刻请求 https://my.ipin.io/info → 得到 `country` → 如果命中黑名单(例如 `TW`)就立即拦截页面的后续展示:不再渲染正常内容,而是直接输出“禁止访问 / 404 / 自定义错误”这类终态信息;否则继续正常加载页面功能与内容。
第二段:为什么前端适合你这个接口
因为是浏览器直接请求第三方接口,所以第三方看到的是“用户的公网 IP”,返回的国家/地区就对应用户本身——这正是你要的效果。
2)代码实现(尽可能简单,偏前端)
2.1 最简单:命中就“禁止访问”(推荐)
把这段放到页面 <head> 里(越靠前越好)。命中后直接覆盖页面内容,并给出明确的“禁止访问”提示。你也可以把文案替换成你自己的合规说明或服务范围说明。
示例 A:如果 country === "TW" 则禁止访问
<script>
(async function () {
try {
const info = await fetch("https://my.ipin.io/info").then(r => r.json());
if (info.country === "TW") {
document.documentElement.innerHTML =
"<head><title>403 Forbidden</title></head>" +
"<body style='font-family:system-ui;padding:40px'>" +
"<h1>403 禁止访问</h1>" +
"<p>你所在的地区暂不提供服务。</p>" +
"</body>";
}
} catch (e) {
// 查不到就先放行(你也可以改成默认禁止访问)
}
})();
</script>
示例 B:如果不是 TW 则禁止访问(等价于“只允许 TW 访问”)
<script>
(async function () {
try {
const info = await fetch("https://my.ipin.io/info").then(r => r.json());
if (info.country !== "TW") {
document.documentElement.innerHTML =
"<head><title>403 Forbidden</title></head>" +
"<body style='font-family:system-ui;padding:40px'>" +
"<h1>403 禁止访问</h1>" +
"<p>该页面仅对指定地区开放。</p>" +
"</body>";
}
} catch (e) {
// 查不到就先放行(你也可以改成默认禁止访问)
}
})();
</script>
示例 C:如果是 TW、US 等则禁止访问(黑名单模式)
<script>
(async function () {
const blocked = ["TW", "US"]; // 命中任意一个就禁止访问
try {
const info = await fetch("https://my.ipin.io/info").then(r => r.json());
if (blocked.includes(info.country)) {
document.documentElement.innerHTML =
"<head><title>403 Forbidden</title></head>" +
"<body style='font-family:system-ui;padding:40px'>" +
"<h1>403 禁止访问</h1>" +
"<p>你所在的地区暂不提供服务。</p>" +
"</body>";
}
} catch (e) {
// 查不到就先放行(你也可以改成默认禁止访问)
}
})();
</script>
2.2 输出 404 Not Found(用于“不可用/不存在”的呈现方式)
有些产品希望对特定地区表现为“页面不存在”,而不是明确提示“被限制”。这种情况下可以用 404 语义的错误页结构覆盖原页面内容。注意:这依然是前端层面的呈现,并不会真正改变服务器返回的 HTTP 状态码,但对“页面访问体验”来说足够直观。
<script>
(async function () {
try {
const { country } = await fetch("https://my.ipin.io/info").then(r => r.json());
if (country === "TW") {
document.documentElement.innerHTML =
"<head><title>404 Not Found</title></head>" +
"<body style='font-family:system-ui;padding:40px'>" +
"<h1>404 Not Found</h1>" +
"<p>The requested URL was not found on this server.</p>" +
"</body>";
}
} catch (e) {}
})();
</script>
2.3 黑名单写成数组(更贴近真实业务)
实际业务里通常不是只拦一个地区,而是维护一个黑名单列表。下面示例依然保持极简:命中任意一个就直接输出“禁止访问”。
<script>
(async function () {
const blocked = ["TW"]; // 需要拦截的国家/地区代码
try {
const info = await fetch("https://my.ipin.io/info").then(r => r.json());
if (blocked.includes(info.country)) {
document.documentElement.innerHTML =
"<head><title>403 Forbidden</title></head>" +
"<body style='font-family:system-ui;padding:40px'>" +
"<h1>403 禁止访问</h1>" +
"<p>你所在的地区暂不提供服务。</p>" +
"</body>";
}
} catch (e) {}
})();
</script>
2.4 React(只给一个最短能用的:命中后直接显示错误页)
import { useEffect, useState } from "react";
export default function App() {
const [blocked, setBlocked] = useState(false);
useEffect(() => {
(async () => {
try {
const info = await fetch("https://my.ipin.io/info").then(r => r.json());
if (info.country === "TW") setBlocked(true);
} catch (e) {}
})();
}, []);
if (blocked) {
return (
<div style={{ fontFamily: "system-ui", padding: 40 }}>
<h1>403 禁止访问</h1>
<p>你所在的地区暂不提供服务。</p>
</div>
);
}
return <div>...</div>;
}
2.5 Vue(同样最短:命中后显示错误内容)
<script>
export default {
data() {
return { blocked: false };
},
async mounted() {
try {
const info = await fetch("https://my.ipin.io/info").then(r => r.json());
if (info.country === "TW") this.blocked = true;
} catch (e) {}
}
}
</script>
<template>
<div v-if="blocked" style="font-family:system-ui;padding:40px">
<h1>403 禁止访问</h1>
<p>你所在的地区暂不提供服务。</p>
</div>
<div v-else>...</div>
</template>
3)在 WordPress / Joomla / Magento / Shopify 等中实现(前端方式 + 代码展示)
下面思路都一样:把一段“超简 JS”插入到全站公共 Head(或布局文件)。由于你的目标是“前端控制 + 错误呈现”,所以只要确保脚本尽早执行,就能在页面主体渲染之前完成地区判断,并在命中时直接显示“禁止访问/404/错误提示”的终态内容。
我建议统一用这段(最简“禁止访问”版):
<script>
(async function () {
try {
const info = await fetch("https://my.ipin.io/info").then(r => r.json());
if (info.country === "TW") {
document.documentElement.innerHTML =
"<head><title>403 Forbidden</title></head>" +
"<body style='font-family:system-ui;padding:40px'>" +
"<h1>403 禁止访问</h1>" +
"<p>你所在的地区暂不提供服务。</p>" +
"</body>";
}
} catch (e) {}
})();
</script>
3.1 WordPress
- 方式 A(最省事):用“插入页眉页脚脚本”的插件,把脚本贴到 Header 区域(全站生效)
- 方式 B:主题文件
`header.php`的</head>前插入脚本
3.2 Joomla! / Joomla
- 通常在当前模板(template)的
`index.php`里找到<head>,把脚本贴到</head>前 - 或者在后台有“自定义代码/自定义 HTML”放入 head 的位置(不同模板入口不同)
3.3 Magento(偏前端注入的做法)
- 如果你只是想“展示层阻止访问”,可以把脚本加到主题的全局 head(主题/layout 的 head 区域)
- Magento 后台也常见有“HTML Head”类似配置项(不同版本/主题略有差异),目标就是让脚本全站进入
<head>
> 你要的是“尽量不写服务端代码”,所以这里就不展开 XML/模块写法了——只要能把脚本插进 head,就能跑。
3.4 Shopify
- 在线商店 → 主题 → 编辑代码
- 找到
`theme.liquid`,在</head>前粘贴脚本(全站生效)
3.5 其他常见系统(通用套路)
- 任何能编辑全局
<head>的系统:把脚本放到 head 最靠前的位置 - 只想限制某几个页面:只在对应页面模板里插脚本即可
4)总结
你这篇文章的核心就是一句话:
> 浏览器请求 https://my.ipin.io/info 得到 { country },如果 country === "TW" 就直接以“禁止访问 / 404 / 自定义错误”的形式终止页面访问,从而实现“按 IP 地区禁止访问”的前端方案。
它实现成本低、上手快、适合“合规提示/区域不可用提示/前端灰度控制”。在多数业务里,前端层的“错误呈现”已经能满足产品与运营诉求:让用户明确知道此页面在其地区不可用,并且避免继续暴露页面主体内容与交互入口。若你需要更严格的“不可绕过”,再叠加服务端或 CDN 层的拦截即可。
5)常见问题(FAQ)
Q1:前端做拦截真的能“禁止访问”吗?
更准确说:它能让“页面呈现层”对访问者给出明确的拒绝结果(例如 403/404/错误提示页),从而阻止正常用户继续使用页面功能。但懂的人可以禁用 JS 或修改脚本,所以它并不是不可绕过的安全墙。如果你需要“绝对禁止”,应该在服务端或 CDN 再做一层拦截;如果你的目标是合规提示与功能不可用提示,前端拦截通常已经足够。
Q2:为什么要把脚本放到 <head>,而且越靠前越好?
因为越早判断,就越能在页面主体渲染之前结束访问流程,减少“先看到内容再被拦截”的闪烁,也避免加载不必要的资源(脚本、图片、组件等)。对用户体验和性能来说,这都是更合理的执行位置。
Q3:如果 https://my.ipin.io/info 请求失败怎么办?
最常见的策略是“失败默认放行”,因为外部接口失败不应误伤正常用户;另一种策略是“失败默认拒绝”,适合强合规场景。你可以根据业务风险选择。无论哪种,都建议保持逻辑简单:成功拿到 country 就判断,拿不到就走你的默认策略。
Q4:会不会遇到 CORS 问题?
有可能:如果接口没有允许浏览器跨域访问,fetch 会被浏览器拦截。遇到这种情况,你需要确保接口端允许跨域请求(例如允许你的域名),否则浏览器侧无法直接读取返回 JSON。对于“前端为主”的方案来说,CORS 是最需要提前确认的一点。
Q5:country 的值是什么标准?
通常是国家/地区的两位字母代码(常见是 ISO 3166-1 alpha-2)。你现在用的 TW 就是典型形式。实际使用时建议统一用大写代码,并在代码中把黑名单也写成同样的格式,避免大小写导致的判断失效。
Q6:如何从“拦截某个国家”改成“只允许某些国家”?
把逻辑反过来就行:
- allowlist:只允许 ["US","JP"],其他全部显示“禁止访问/错误提示”
- blocklist:只拦截 ["TW"],其他放行
两种方式本质都是判断 country 是否命中列表,只是默认行为不同。