优化检查 url 段问题的方法

Optimize a method that checks url segments issue

我一直在 Laravel 中间件中使用一种方法来检查任何 URL 段中的字符串,如果它与“黑名单”字符串匹配则阻止 IP。

一开始,我只有几个字符串要检查,但现在,列表越来越长,当我试图优化它以使用 blacklist array 时,我最终陷入了混乱代码和我的想法。

我相信这是可以做到的,但无法找出优化此中间件的最佳方法。下面是中间件代码示例,其中包含我遇到问题的注释。

handle($request, Closure $next) 方法中为所有列入黑名单的字符串调用 $this->inUrl() 方法。

我尝试添加一个 protected $blacklisted 数组,用于 $this->inUrl() 但无法使其工作。

提前感谢您提出任何建议,我们将不胜感激和欢迎。我也在考虑在优化时将代码作为要点提供给 GitHub。

namespace App\Http\Middleware;

/**
 * Class VerifyBlacklistedRequests
 *
 * @package App\Http\Middleware
 */
class VerifyBlacklistedRequests
{

    /**
     * The array of blacklisted request string segments
     *
     * @access protected
     * @var array|string[]
     */
    protected array $blacklisted = [
        '.env', '.ftpconfig', '.vscode', ',git', '.git/HEAD'
        // etc...
    ];

    /**
     * Handle an incoming request.
     *
     * @access public
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure                 $next
     *
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if($this->inUrl('.env')
            || $this->inUrl('.ftpconfig')
            || $this->inUrl('.vscode')
            || $this->inUrl('.git')
            || $this->inUrl('.git/HEAD')
 
           // many more checks below the above ones

        ) {
            // logic that blocks the IP goes here and working fine
        }

        return $next($request);
    }

     /**
     * Check if the string is in any URL segment or at the one specified.
     *
     * @access protected
     *
     * @param string|mixed $value   Segment value/content.
     * @param integer      $segment Segment position.
     *
     * @return bool
     */
    protected function inUrl(string $value, $segment = -1)
    {
        if($segment !== -1 && request()->segment($segment) === $value) {
            return true;
        }

        collect(request()->segments())->each(function ($segment) use ($value) {
            if($segment === $value) {
                return true;
            }
        });

        return false;
    }

}

我不知道这样做是否会优化代码,但我认为代码更具可读性。

namespace App\Http\Middleware;

/**
 * Class VerifyBlacklistedRequests
 *
 * @package App\Http\Middleware
 */
class VerifyBlacklistedRequests
{

    /**
     * The array of blacklisted request string segments
     *
     * @access protected
     * @var array|string[]
     */
    protected array $blacklisted = [
        '.env', '.ftpconfig', '.vscode', ',git', '.git/HEAD'
        // etc...
    ];

    /**
     * Handle an incoming request.
     *
     * @access public
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure                 $next
     *
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        //loop over the list instead of that long conditions
        foreach($this->blacklisted as $blacklistedItem) {
            if($this->inUrl($blacklistedItem)) 
            {
                // logic that blocks the IP goes here and working fine
            }
        }
    }

     /**
     * Check if the string is in any URL segment or at the one specified.
     *
     * @access protected
     *
     * @param string|mixed $value   Segment value/content.
     * @param integer      $segment Segment position.
     *
     * @return bool
     */
    protected function inUrl(string $value, $segment = -1)
    {
        if($segment !== -1 && request()->segment($segment) === $value) {
            return true;
        }

        foreach(request()->segments() as $segment) {
            if($segment === $value) {
                return true;
            }
        }

        return false;
    }

}

我建议你创建文字路由,这样更容易维护。转到 RouteServiceProvider 并创建一个类似于 web 或 api 的新阅读,因此该新文件中的任何路由都会 ban/block IP。

我相信你可以使用 DB 并在 DB 中将黑名单设为 indexed, 数据库将使用其内部引擎自行处理和搜索。

protected function urlWasInBlackList($segment = -1){
    $segmentStrings= $segment != -1 ? [request()->segment($segment)] : request()->segments();

    return DB::table('blacklisted')->select('*')->whereIn('pattern',$segmantStrings)->exists()
}

并使用类似这样的函数代替 inUrl()。它将检查 Against DB 的禁止词。


更新:避免数据库

但是 DB 使用其 InnoDB 引擎快速处理这种情况, 也许你想避免数据库,因为连接开销或者只是因为一个功能而不依赖你的项目,

如果你想避免数据库, 你必须检查 url 每个 黑名单词 循环 并且它将是 O(n), 如果你乘以 节计数 ,它将是 O(n*m)

最佳方法是避免循环,并像 DB 那样制作 散列 table

所以我在 php 中寻找 hash_tables 并在 php array doc 中找到 php 关联数组是 哈希映射 。 并且函数 array_key_exists() 正在寻找键的散列 table,您可以在 php 源代码中看到 array_key_exists() 代码,这些代码解决了 ht 作为这里的哈希映射:

array.c 行 #6071

zen_hash.h 第 529 行

所以我建议使用这样的东西:

protected array $blacklisted = [
        '.env' => null, '.ftpconfig' => null, '.vscode' => null, ',git' => null, '.git/HEAD' => null
      
    ];

用于黑名单定义。 和

    $segmentStrings= $segment != -1 ? [request()->segment($segment)] : request()->segments();

foreach(segmentStrings as $segmentString){
    return array_key_exists($segmentString, $blacklisted);
}

在所有的建议之后,请在这里张贴,我最终得到了一个使用一些建议方法的解决方案。

结果是将页面加载时间减少了 1 秒以上。

我的最终实现:

  1. 创建了一个配置文件 security.php,其中包含列入黑名单的请求字符串和列入白名单的 IP 的候选名单。

security.php配置文件

<?php
return [
    /*
    |--------------------------------------------------------------------------
    | Whitelisted IPs configuration
    |--------------------------------------------------------------------------
    |
    | These are the settings for the whitelisted IPs. The array contains
    | the IPs that should not trigger the IP block.
    |
    */
    'whitelisted_ips' => [
        // whitelisted IPs array
    ],

    /*
    |--------------------------------------------------------------------------
    | Blacklisted request strings configuration
    |--------------------------------------------------------------------------
    |
    | These are the settings for the blacklisted request strings. The array contains
    | the strings that should trigger the IP to be blocked.
    |
    */
    'blacklisted_requests' => [
        '.env',
        '.ftpconfig',
        '.vscode',
        '.git',
        '.git/HEAD',
        '_profiler',
        '__media__',
        'administrator',
        //...
    ];
];
  1. 优化了中间件,删除了 inUrl() 方法中的循环

VerifyBlacklistedRequests 中间件

<?php
namespace App\Http\Middleware;

use Closure;

/**
 * Class VerifyHackingAttemptsRequests
 *
 * @property \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed white_listed_ips
 * @property \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed blacklist
 * @package App\Http\Middleware
 */
class VerifyHackingAttemptsRequests
{
    /**
     * @access protected
     * @var \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed
     */
    protected $blacklist;

    /**
     * @access protected
     * @var \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed
     */
    protected $white_listed_ips;

    /**
     * VerifyHackingAttemptsRequests constructor
     *
     * @access public
     */
    public function __construct()
    {
        $this->blacklist        = config('security.blacklisted_requests');
        $this->white_listed_ips = config('security.whitelisted_ips');
    }

    /**
     * Handle an incoming request.
     *
     * @access public
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure                 $next
     *
     * @return mixed
     * @since  2.8.1
     */
    public function handle($request, Closure $next)
    {
        $exists = false;

        foreach(request()->segments() as $segment) {
            if(in_array($segment, $this->blacklist)) {
                $exists = true;
            }
        }

        if($exists) {
            $this->blockIp($request)
        }

        return $next($request);
    }

    /**
     * Method to save an IP in the Blocked IP database table
     *
     * @access protected
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \App\Models\BlockedIp
     */
    protected function blockIp(Request $request, $notes = null)
    {
        // the logic to persist the data through the BlockedIp model
    }
}

总而言之,删除了 inUrl() 方法,删除了所有循环和方法调用,并且如上所述,页面加载时间缩短了 50% 以上。

感谢所有帮助我解决问题的建议方法。