Laravel项目IP黑名单,IP白名单,IP访问频率控制中间件

实际上Laravel自带有访问控制路由中间件 throttle

默认是 throttle:60,1
就是限制同一个IP在1分钟60次请求,超出的会被拦截,下一分钟才会恢复访问,它没有黑名单和白名单的功能,需要自己重新实现。

存储方式同cache driver

关于throttle:
https://learnku.com/articles/20073
https://learnku.com/articles/27682
https://segmentfault.com/a/1190000013499082

此处的频率是任意接口的访问总和,如果想限制到每一个接口,则需要自己拷贝 throttle 重新实现了。

以下是自己写的中间件,可定制功能。

<?php

namespace App\Http\Middleware;

use Closure;

use App\Traits\Controller\OpApiAjaxTraits;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;

class GateWayPlus
{
    use  OpApiAjaxTraits;

    const IP_LIMIT_NUM_KEY = 'ipLimit:ipLimitNum';
    const IP_BLACK_LIST_KEY = 'ipLimit:ipBlackList';
    public $prefix = 'gateway';
    public $delaySeconds = 60; // 观察时间跨度,秒
    public $maxAttempts = 10000; // 限制请求数
    public $blackSeconds = 0; // 封禁时长,秒,0-不封禁

    /**
     * @param \Illuminate\Http\Request $request
     * @param Closure $next
     * @param int $maxAttempts
     * @param int $delaySeconds
     * @param int $blackSeconds
     * @param string $prefix
     * @return \App\Traits\Controller\json|mixed
     */
    public function handle($request, Closure $next, $prefix = null, $delaySeconds = null, $maxAttempts = null, $blackSeconds = null)
    {
        $path     = $request->path();
        $clientIp = $request->getClientIp();

        if (!is_null($prefix) && !empty($prefix)) {
            $this->prefix = $prefix;
        }
        // redis配置集群时必须
        $this->prefix = '{' . $this->prefix . '}';
        if (!is_null($maxAttempts)) {
            $this->maxAttempts = intval($maxAttempts);
        }
        if (!is_null($delaySeconds)) {
            $this->delaySeconds = intval($delaySeconds);
        }
        if (!is_null($blackSeconds)) {
            $this->blackSeconds = intval($blackSeconds);
        }

        $param  = [
            'path'     => $path,
            'clientIp' => $clientIp,
        ];
        $result = $this->main($param);
        if ($result === false) {
            return $this->ajaxApiError('当前IP请求过于频繁,暂时被封禁~');
        }

        return $next($request);
    }

    private function main($param)
    {
        // 预知的IP黑名单
        $blackList = [];
        if (in_array($param['clientIp'], $blackList)) {
            return false;
        }

        // 预知的IP白名单
        $whiteList = [];
        if (in_array($param['clientIp'], $whiteList)) {
            return true;
        }

        $blackKey = $this->prefix . ':' . self::IP_BLACK_LIST_KEY;
        $limitKey = $this->prefix . ':' . self::IP_LIMIT_NUM_KEY;

        $time = time();
        $item = md5($param['path'] . '|' . $param['clientIp']);

        return $this->luaScript($blackKey, $limitKey, $item, $time);
    }

    /*
     * 普通模式
     */
    public function normal($blackKey, $limitKey, $item, $time)
    {
        if ($this->blackSeconds > 0) {
            $timeout = intval(Redis::hget($blackKey, $item));
            if ($timeout) {
                if ($timeout > $time) {
                    // 未解封
                    return false;
                }
                // 已解封,移除黑名单
                Redis::hdel($blackKey, $item);
            }
        }

        $last = intval(Redis::hget($limitKey, $item));
        if ($last >= $this->maxAttempts) {
            return false;
        }

        $num = Redis::hincrby($limitKey, $item, 1);
        if (Redis::ttl($limitKey) == -1) {
            Redis::expire($limitKey, $this->delaySeconds);
        }

        if ($num >= $this->maxAttempts && $this->blackSeconds > 0) {
            // 加入黑名单
            Redis::hset($blackKey, $item, $time + $this->blackSeconds);
            // 删除记录
            Redis::hdel($limitKey, $item);
        }

        return true;
    }

    /*
     * LUA脚本模式
     * 支持redis集群部署
     */
    public function luaScript($blackKey, $limitKey, $item, $time)
    {
        $script = <<<'LUA'
local blackSeconds = tonumber(ARGV[5])
if(blackSeconds > 0)
then
    local timeout = redis.call('hget', KEYS[1], ARGV[1])
    if(timeout ~= false)
    then
        if(tonumber(timeout) > tonumber(ARGV[2]))
        then
            return false
        end
        redis.call('hdel', KEYS[1], ARGV[1])
    end
end

local last = redis.call('hget', KEYS[2], ARGV[1])
if(last ~= false and tonumber(last) >= tonumber(ARGV[3]))
then
    return false
end

local num = redis.call('hincrby', KEYS[2], ARGV[1], 1)
local ttl = redis.call('ttl', KEYS[2])
if(ttl == -1)
then
    redis.call('expire', KEYS[2], ARGV[4])
end

if(tonumber(num) >= tonumber(ARGV[3]) and blackSeconds > 0)
then 
    redis.call('hset', KEYS[1], ARGV[1], ARGV[2] + ARGV[5])
    redis.call('hdel', KEYS[2], ARGV[1])
end
return true
LUA;

        $result = Redis::eval($script, 2, $blackKey, $limitKey, $item, $time, $this->maxAttempts, $this->delaySeconds, $this->blackSeconds);
        if ($result) {
            return true;
        } else {
            return false;
        }
    }

}


注册中间件

路由中的使用

$router->group(['namespace' => 'xxx', 'prefix' => 'yyy', 'middleware' => ['GateWayPlus:zzz,60,2,120']], function () use ($router) { 
	
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 .NET Core MVC 中实现 IP 白名单段可以通过中间件来实现。具体步骤如下: 1. 创建一个中间件类并实现 `IMiddleware` 接口。 ```csharp public class IpWhitelistMiddleware : IMiddleware { private readonly RequestDelegate _next; private readonly List<string> _whitelist; public IpWhitelistMiddleware(RequestDelegate next, List<string> whitelist) { _next = next; _whitelist = whitelist; } public async Task InvokeAsync(HttpContext context, Func<Task> next) { var ipAddress = context.Connection.RemoteIpAddress; if (!IsIpWhitelisted(ipAddress)) { context.Response.StatusCode = (int)HttpStatusCode.Forbidden; return; } await next(); } private bool IsIpWhitelisted(IPAddress ipAddress) { return _whitelist.Any(ip => IPAddress.Parse(ip).Equals(ipAddress)); } } ``` 2. 在 `Startup.cs` 文件中注册中间件。 ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseMiddleware<IpWhitelistMiddleware>(new List<string> { "127.0.0.1", "192.168.0.0/16" }); // ... } ``` 在上面的示例中,我们将 IP 白名单列表作为中间件的构造函数参数传递,并在 `InvokeAsync` 方法中验证客户端 IP 是否在白名单范围内。如果 IP 不在白名单中,则返回 HTTP 状态码 403 Forbidden。在 `Startup.cs` 文件的 `Configure` 方法中,我们将中间件注册到应用程序中。注意,在 `UseMiddleware` 方法中,我们使用了泛型参数来指定要注册的中间件类型,同时也将 IP 白名单列表传递给了中间件构造函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值