IP 检测,Web3 的项目方是如何过滤某些 IP 以及屏蔽某些区域的用户的?
我们在访问一些 Web3 网站的时候,会经常碰到网页显示我们的地区不在服务范围之内。
比如 unisat 的官网、secwarex 的官网等等。
这期视频不是教大家如何绕开这种限制,相信以大家的聪明才智绕开这种限制轻而易举。
这期视频主要是讲解一下如何实现对 IP 的检测、识别与屏蔽。
为什么要做 IP 检测?
首先我们要明白为什么要做 IP 检测这件事?
因为 Web3 并不是一个在全世界都受欢迎的行业,有些国家和地区对 Web3 并不喜欢,甚至是一种厌恶或者排斥的态度。
有一些业务在一些国家和地区是不可以运营的,否则会触犯到法律。
所以一些项目方会把某些国家和地区的 IP 禁用掉,这样就可以规避法律风险。
规避法律风险是主要的目的,除此之外我们还可以做一些功能,比如针对某个地区的用户提供特殊的内容,比如一些活动之类的。并且可以自动根据 IP 来切换用户所在区域的语言。或者是提供当地的新闻之类的个性化服务。
IP 的格式
IP 是 Internet Protocol 的缩写,也就是互联网协议。
IP 的概念其实很好理解,世界上每一台设备连接到互联网都会有一个唯一的 IP。
IP 目前有两个版本,分别是 IPv4 和 IPv6。
IPv4 是目前使用最广泛的版本,它是由 32 位的二进制组成。通常又分为 4 组,每组占据 1 个字节,也就是 8 位。我们会用 10 进制数来表示 2 进制的字节。
它的格式是这样的: 192.168.1.1
。因为 8 位的二进制的范围是 0 到 255,所以每一组最多是三位数,最小是 0,最大是 255。
其实我们不难发现,组成 IPv4 的地址个数是有限的,目前一共只有 43 亿个唯一的 IPv4 地址,实际上全球的设备数量已经超过了 43 亿个。
IPv6 是为了应对 IPv4 地址耗尽而设计的新一代 IP 地址。IPv6 地址由八组四位十六进制数表示。另一个和 IPv4 的区别是,IPv6 的格式不同,它是通过冒号(:)对每一组进行分隔的。比如: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
IPv6 理论上可以提供 2 的 128 次方个唯一地址,完全可以满足全球范围内长期的网络设备增长需求。
通过 IP 识别区域的方式
一般来说,通过 IP 识别区域的方式有三种。
第一种是直接通过专门做站点防护的服务商,在网络层面来做。比较出名的有 Cloudflare,还有 AWS 这类服务商也会提供这种服务。
第二种是使用在线的第三方 IP 服务商。它们通常会提供 API 接口,我们把 IP 传递到它们的接口,就可以得到用户的区域。比较知名的服务商有 MaxMind、ipstack 和 ipinfo.io 等。
第三种就是自建 IP 地理定位服务。我们可以选择购买或者使用开源的地理位置数据库,建立自己的服务来查询 IP 数据。这种方法可以更好的控制数据隐私和响应时间。
技术实现
这里我选用开源的 geoip lite 这个项目来完成自建 IP 地理定位的功能。
然后我会把它应用到 Nodejs 中比较流行的 Web 框架 Nestjs 中,并把它做成一个中间件。
首先来创建 nestjs 项目。
nest new geoip
然后通过 npm run start:dev
来启动项目。
然后访问:http://0.0.0.0:3000/
,可以看到 Hello World!
的字样。
然后安装 geoip lite
。
npm install geoip-lite
我们再继续新建一个中间件,我们的核心逻辑都会放在这个中间件中。
import {
Injectable,
NestMiddleware,
ServiceUnavailableException,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as geoip from 'geoip-lite';
@Injectable()
export class GeoIpMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction): void {
// 从请求中获取 IP 地址
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
// 如果 IP 地址是字符串类型,使用 geoip-lite 模块获取 IP 地址的地理位置信息
if (typeof ip === 'string') {
const geo = geoip.lookup(ip);
console.debug('geo info:', ip, geo);
// 如果地理位置信息中的国家是中国,则抛出 ServiceUnavailableException 异常
if (geo && geo.country === 'CN') {
throw new ServiceUnavailableException();
}
} else {
// 如果 IP 地址不是字符串类型,抛出 ServiceUnavailableException 异常
throw new ServiceUnavailableException();
}
next();
}
}
然后把中间件添加到 app.module.ts
中,这样才会生效。
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GeoIpMiddleware } from './geoip.middleware';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(GeoIpMiddleware)
.forRoutes({ path: '*', method: RequestMethod.ALL });
}
}
现在我们就可以访问服务来测试结果了。
不过我们不能在本地进行测试,因为本地访问 IP 总是 127.0.0.1,无法获取到真正的地理位置。
所以我把项目部署到服务器上进行测试。
可以看到,我开启代理之后,地理位置检测到是香港,正常访问。
然后我把代理关闭,地理位置检测到的是大陆,所以请求返回了 503,服务不可用。
转载自:https://juejin.cn/post/7361687968518783010