Dotnet Core Public API的安全实践

公开API的安全,其实更重要。html

1、API的安全

做为一个Dotnet Core的老司机,写API时,能兼顾到API的安全,这是一种优雅。前端

一般,咱们会用认证来保证API的安全,无敌的Authorize能解决咱们不少的问题。git

可是,总有一些场合,咱们没办法用Authorize,而只能用匿名或不加验证的方式来访问。比方电商中查询SKU的列表并在前端展现,一般这个无关用户和权限,在完成API的时候,咱们也不会加入认证Authorizegithub

这种状况下,若是直接写,不加入安全级别,这样的体系结构是有可能成为可供利用的安全漏洞的。web

Dotnet Core框架已经提供了一些常见漏洞的解决方法,包括:json

  • 跨站点脚本
  • SQL注入
  • 跨站点请求伪造(CSRF)
  • 重定向

等等。c#

可是,咱们还须要更进一步,还须要照顾到如下常见的攻击:api

  • 拒绝服务(DOS)
  • 分布式拒绝服务(DDOS)
  • 批量API调用
  • 探测响应
  • 数据抓取

这部份内容,须要咱们本身实现。固然,这部份内容的实现,也能够从Web Server上进行设置。安全

本文讨论的,是代码的实现。微信

    为了防止不提供原网址的转载,特在这里加上原文连接:http://www.javashuo.com/article/p-zdgwvixb-np.html

2、相关代码

今天偷个懒,不讲原理,以分享代码为主。

2.1 基于IP的客户端请求限制

经过限制客户端在指定的时间范围内的请求数量,防止恶意bot攻击。

代码中,我创建了一个基于IP的请求限制过滤器。

注意:有多个客户端位于同一个IP地址的状况,这个状况在这个代码中没有考虑。若是您但愿实现这一点,能够把几种方式结合起来使用。

如下是代码:

[AttributeUsage(AttributeTargets.Method)]
public class RequestLimitAttribute : ActionFilterAttribute
{
    public string Name { get; }
    public int NoOfRequest { get; set; }
    public int Seconds { get; set; }

    private static MemoryCache Cache { get; } = new MemoryCache(new MemoryCacheOptions());

    public RequestLimitAttribute(string name, int noOfRequest = 5int seconds = 10)
    
{
        Name = name;
        NoOfRequest = noOfRequest;
        Seconds = seconds;
    }
    public override void OnActionExecuting(ActionExecutingContext context)
    
{
        var ipAddress = context.HttpContext.Request.HttpContext.Connection.RemoteIpAddress;
        var memoryCacheKey = $"{Name}-{ipAddress}";

        Cache.TryGetValue(memoryCacheKey, out int prevReqCount);
        if (prevReqCount >= NoOfRequest)
        {
            context.Result = new ContentResult
            {
                Content = $"Request limit is exceeded. Try again in {Seconds} seconds.",
            };
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.TooManyRequests;
        }
        else
        {
            var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(Seconds));
            Cache.Set(memoryCacheKey, (prevReqCount + 1), cacheEntryOptions);
        }
    }
}

使用时,只要在须要的API前加属性便可:

[HttpGet]
[RequestLimit("DataGet"530)]
public IEnumerable<WeatherForecast> Get()
{
    ...
}

2.2 引用头检查

对API请求的请求引用头进行检查,能够防止API滥用,以及跨站点请求伪造(CSRF)攻击。

一样,也是采用自定义属性的方式。

public class ValidateReferrerAttribute : ActionFilterAttribute
{
    private IConfiguration _configuration;

    public override void OnActionExecuting(ActionExecutingContext context)
    
{
        _configuration = (IConfiguration)context.HttpContext.RequestServices.GetService(typeof(IConfiguration));

        base.OnActionExecuting(context);

        if (!IsValidRequest(context.HttpContext.Request))
        {
            context.Result = new ContentResult
            {
                Content = $"Invalid referer header",
            };
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.ExpectationFailed;
        }
    }
    private bool IsValidRequest(HttpRequest request)
    
{
        string referrerURL = "";

        if (request.Headers.ContainsKey("Referer"))
        {
            referrerURL = request.Headers["Referer"];
        }
        if (string.IsNullOrWhiteSpace(referrerURL)) return true;

        var allowedUrls = _configuration.GetSection("CorsOrigin").Get<string[]>()?.Select(url => new Uri(url).Authority).ToList();

        bool isValidClient = allowedUrls.Contains(new Uri(referrerURL).Authority);

        return isValidClient;
    }
}

这里我用了一个配置,在appsetting.json中:

{
  "CorsOrigin": ["https://test.com""http://test1.cn:8080"]
}

CorsOrigin参数中加入容许引用的来源域名:端口列表。

使用时,在须要的API前加属性:

[HttpGet]
[ValidateReferrer]
public IEnumerable<WeatherForecast> Get()
{
    ...
}

2.3 DDOS攻击检查

DDOS攻击在网上很常见,这种攻击简单有效,可让一个网站瞬间开始并长时间没法响应。一般来讲,网站能够经过多种节流方法来避免这种状况。

下面咱们换一种方式,用中间件MiddleWare来限制特定客户端IP的请求数量。

public class DosAttackMiddleware
{

    private static Dictionary<stringshort> _IpAdresses = new Dictionary<stringshort>();
    private static Stack<string> _Banned = new Stack<string>();
    private static Timer _Timer = CreateTimer();
    private static Timer _BannedTimer = CreateBanningTimer();

    private const int BANNED_REQUESTS = 10;
    private const int REDUCTION_INTERVAL = 1000// 1 second    
    private const int RELEASE_INTERVAL = 5 * 60 * 1000// 5 minutes    
    private RequestDelegate _next;

    public DosAttackMiddleware(RequestDelegate next)
    
{
        _next = next;
    }
    public async Task InvokeAsync(HttpContext httpContext)
    
{
        string ip = httpContext.Connection.RemoteIpAddress.ToString();

        if (_Banned.Contains(ip))
        {
            httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
        }

        CheckIpAddress(ip);

        await _next(httpContext);
    }

    private static void CheckIpAddress(string ip)
    
{
        if (!_IpAdresses.ContainsKey(ip))
        {
            _IpAdresses[ip] = 1;
        }
        else if (_IpAdresses[ip] == BANNED_REQUESTS)
        {
            _Banned.Push(ip);
            _IpAdresses.Remove(ip);
        }
        else
        {
            _IpAdresses[ip]++;
        }
    }


    private static Timer CreateTimer()
    
{
        Timer timer = GetTimer(REDUCTION_INTERVAL);
        timer.Elapsed += new ElapsedEventHandler(TimerElapsed);
        return timer;
    }

    private static Timer CreateBanningTimer()
    
{
        Timer timer = GetTimer(RELEASE_INTERVAL);
        timer.Elapsed += delegate {
            if (_Banned.Any()) _Banned.Pop();
        };
        return timer;
    }

    private static Timer GetTimer(int interval)
    
{
        Timer timer = new Timer();
        timer.Interval = interval;
        timer.Start();
        return timer;
    }

    private static void TimerElapsed(object sender, ElapsedEventArgs e)
    
{
        foreach (string key in _IpAdresses.Keys.ToList())
        {
            _IpAdresses[key]--;
            if (_IpAdresses[key] == 0) _IpAdresses.Remove(key);
        }
    }
}

代码中设置:1秒(1000ms)中有超过10次访问时,对应的IP会被禁用5分钟。

使用时,在Startup.cs中直接加载中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseMiddleware<DosAttackMiddleware>();
    ...
}

3、结尾的话

以上代码仅为抛砖引玉之用。

公开的API,未经验证的API,在生产环境会由于种种缘由被攻击。这几天公司的系统就由于这个出了大事。

因此,写API的时候,要充分考虑到这些网络攻击的可能性,经过正确的处理,来防止来自网络的攻击。

这是一份责任,也是一个理念。

与你们共勉!

(全文完)

本文的代码,我已经传到Github上,位置在:https://github.com/humornif/Demo-Code/tree/master/0021/demo

 


 

微信公众号:老王Plus

扫描二维码,关注我的公众号,能够第一时间获得最新的我的文章和内容推送

本文版权归做者全部,转载请保留此声明和原文连接

相关文章
相关标签/搜索