本文概述
一些应用程序管理员选择根据用户的位置拒绝对用户的访问。这被称为地理封锁, 例如, 它可以在某些购物网站上使用, 这些网站可能选择不接待来自不向其运送商品的国家/地区的访问者。如果你愿意在Symfony 5项目中实现此功能, 那么你找到了正确的位置。
本教程遵循上一篇文章的一些步骤, 其中我们解释了如何从Symfony 3中的访问者IP中检测城市, 国家和地区, 但进行了修改, 即应限制对整个网站的访问。
1.下载GeoLite2免费数据库
第一步, 你将需要项目中或在系统级别可访问的GeoLite二进制数据库。你可以从此处免费从官方的MaxMind GeoLite2下载数据库。在此页面中, 你需要注册一个GeoLite2帐户:
创建帐户并按照通过电子邮件收到的步骤操作后, 你将可以私下下载MaxMind数据库, 在这种情况下, 我们将使用GeoLite2国家/地区版本:
在本教程中, 我们将使用数据库并将其包含在我们自己的项目中的symfony项目的/ private目录中(请注意, 该目录不存在, 因此需要创建, 你可以根据以下内容更改数据库的路径:你的需求), 我们将使用数据库的国家/地区版本, 使我们能够获取本文中提到的信息, 特别是访问者IP的国家/地区。数据库是使用tar压缩的, 因此你可以使用以下命令从命令行提取其内容:
tar -xzf GeoLite2-Country_20200121.tar.gz
另外, 在Windows等其他操作系统中, 你也可以使用7Zip, Winrar或其他解压缩工具来提取其内容。现在你的项目中已有数据库, 目录结构应如下所示:
project/
├── bin
├── composer.json
├── composer.lock
├── config
├── nbproject
├── phpunit.xml.dist
├── private/
│ └── geolite2-country/
│ ├── COPYRIGHT.txt
│ ├── GeoLite2-Country.mmdb
│ └── LICENSE.txt
├── public
├── src
├── symfony.lock
├── templates
├── tests
├── translations
├── var
└── vendor
2.安装MaxMind GeoIP2 PHP API
为了读取数据库, 你不需要托管在MySQL或其他数据库管理器中。数据库具有来自GeoIP的创建者的特殊格式, 即MaxMind DB。 MaxMind DB文件格式是一种数据库格式, 可以使用有效的二进制搜索树将IPv4和IPv6地址映射到数据记录。二进制数据库分为三个部分:
- 二进制搜索树。树的每个级别对应于IPv6地址的128位表示形式中的单个位。
- 数据部分。这些是针对特定IP地址返回给客户端的值, 例如”美国”, “纽约”或由多个字段组成的更复杂的地图类型。
- 数据库元数据。有关数据库本身的信息。
有关此项目使用的数据库类型的更多信息, 请在此处阅读有关MaxMind DB的更多信息。
现在, 我们需要一种用于这种数据库格式的解析器。幸运的是, MaxMind团队为PHP编写了一个很棒的库, 该库使与数据库的交互变得非常容易, 并且你仅需几行代码就可以检索有关用户IP的地理信息。我们正在谈论的是MaxMind GeoIP PHP Api, 该软件包为GeoIP2 Web服务和数据库提供了API。该API还可以与免费的GeoLite2数据库(我们正在使用的数据库)一起使用。你可以使用运行以下命令的composer将此软件包安装在Symfony项目中:
composer require geoip2/geoip2
有关此库的更多信息, 请访问Github上的官方存储库。安装软件包后, 你将可以在Symfony的控制器上使用其类。
3.创建请求监听器
在每个Symfony应用程序上, 很多事情发生在幕后, 因此我们需要知道什么时候发生。 Symfony通过事件通知你何时发生这种情况, 它在处理HTTP请求时会触发与内核相关的多个事件。这正是我们要识别用户所在国家/地区并确定他是否应具有访问权限的地方。逻辑将如下所示, 在项目源的EventListener目录内创建一个RequestListener类。此类仅在构造函数中接收2个参数, 第一个是可以使用services.yaml文件中的%kernel.project_dir%变量注入到该类中的项目的绝对路径。第二个参数是模板引擎(Twig), 因此我们可以呈现一个视图, 该视图将通知用户该网站已被阻止。
此外, 该类将具有一个私有数组变量, 该变量将包含无法访问网站的国家/地区的ISO代码, 你可以根据自己的需要进行更新, 在本例中, 我们将仅阻止4个国家/地区:
- 哥伦比亚
- 巴西
- 玻利维亚
- 美国
侦听器类将响应Symfony的onKernelRequest事件, 并将在运行时调用RestrictAccessOnDisallowedCountries方法, 并提供RequestEvent变量作为第一个参数。提到的方法将读取GeoLite2数据库, 并检查用户IP的国家/地区。如果有结果, 它将获取国家/地区的ISO代码, 并会检查访客代码是否在黑名单中;如果存在, 则会使用自定义的Twig视图发送新的响应, 其中包含针对用户的消息(请注意, 响应代码将为” 403禁止”):
<?php
// src/EventListener/RequestListener.php
namespace App\EventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;
// 1. Include GeoIp2 Classes
use GeoIp2\Database\Reader;
use GeoIp2\Exception\AddressNotFoundException;
class RequestListener
{
// Store the absolute path of the project injected through the service
private $projectDir;
/* @var $twig \Twig\Environment */
private $twig;
/**
* An array with all the ISO codes of the countries where the website shouldn't be accessible
*
* @var Array
*/
private $blacklist = [
"CO", // Colombia
"BO", // Bolivia
"BR", // Brazil
"US", // United States
];
public function __construct(Environment $twigEnvironment, $projectDir)
{
$this->projectDir = $projectDir;
$this->twig = $twigEnvironment;
}
/**
* Run the verification of the users country on every request.
*
* @param RequestEvent $event
* @return type
*/
public function onKernelRequest(RequestEvent $event)
{
if (!$event->isMasterRequest()) {
// don't do anything if it's not the master request
return;
}
$this->RestrictAccessOnDisallowedCountries($event);
}
private function RestrictAccessOnDisallowedCountries(RequestEvent $event)
{
/* @var $request \Symfony\Component\HttpFoundation\Request */
$request = $event->getRequest();
// Declare the path to the GeoLite2-City.mmdb file (database)
$GeoLiteDatabasePath = $this->projectDir . '/private/geolite2-country/GeoLite2-Country.mmdb';
// Create an instance of the Reader of GeoIp2 and provide as first argument
// the path to the database file
$reader = new Reader($GeoLiteDatabasePath);
// Check against the GeoLite database the user's country through his IP
try{
// You can as well test with a fixed IP, for example one of USA in Minessota:
// $reader->country('128.101.101.101');
// However for production, request the client ip:
// $reader->country($request->getClientIp());
/* @var $record \GeoIp2\Model\Country */
$record = $reader->country($request->getClientIp());
$isoCode = $record->country->isoCode;
// If the obtained iso code matches with one of the blacklisted countries, block the access
// rendering a custom page
if(in_array($isoCode, $this->blacklist)){
$response = new Response();
$response->setStatusCode(Response::HTTP_FORBIDDEN);
// Render some twig view, in our case we will render the blocked.html.twig file
$response->setContent($this->twig->render("pages/blocked.html.twig", [
'code' => $isoCode
]));
// Return an HTML file
$response->headers->set('Content-Type', 'text/html');
// Send response
$event->setResponse($response);
}
} catch (AddressNotFoundException $ex) {
// Couldn't retrieve geo information from the given IP
// Is up to you if you want to block the access to the website anyway here ...
}
}
}
请注意, 如果无法确定访问者所在的国家/地区, 此方法不会执行任何操作。如你所见, 在代码中, 我们呈现了以下Twig视图:
{# application/templates/pages/blocked.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Website Blocked</title>
{% block stylesheets %}{% endblock %}
</head>
<body>
<h1>Access Disallowed</h1>
<p>This website doesn't work in your country ({{ code }})</p>
</body>
</html>
4.在services.yaml文件上注册事件监听器
最后, 为了启用事件监听器, 与自动装配的服务不同, 你将需要在services.yaml文件上注册事件监听器, 如下所示:
# /application/config/services.yaml
services:
App\EventListener\RequestListener:
tags:
- { name: kernel.event_listener, event: kernel.request }
bind:
$projectDir: '%kernel.project_dir%'
如步骤3所述, 我们将项目目录绑定为参数。就是这样!清除项目的缓存, 然后尝试通过VPN访问你的应用程序, 或为侦听器手动设置固定的ip, 然后检查地理封锁是否正常工作。
快乐编码!
评论前必须登录!
注册