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

一、API的安全

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

通常,我们会用认证来保证API的安全,无敌的Authorize能解决我们很多的问题。

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

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

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

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

等等。

但是,我们还需要更进一步,还需要照顾到以下常见的攻击:

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

这部分内容,需要我们自己实现。当然,这部分内容的实现,也可以从Web Server上进行设置。

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

    为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/13471718.html

二、相关代码

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

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 = 5, int 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", 5, 30)]
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<string, short> _IpAdresses = new Dictionary<string, short>();
    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>();
    ...
}

三、结尾的话

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

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

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

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

与大家共勉!

(全文完)

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


微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

Dotnet Core Public API的安全实践的更多相关文章

  1. 在dotnet core web api中支持CORS(跨域访问)

    最近在写的Office add-in开发系列中,其中有一个比较共性的问题就是在add-in的客户端脚本中访问远程服务时,要特别注意跨域访问的问题. 关于CORS的一些基本知识,请参考维基百科的说明:h ...

  2. 在ASP dot Net Core MVC中用Controllers调用你的Asp dotnet Core Web API 实现CRUD到远程数据库中,构建你的分布式应用(附Git地址)

    本文所有的东西都是在dot Net Core 1.1环境+VS2017保证测试通过. 本文接着上次文章接着写的,不了解上篇文章的可能看着有点吃力.我尽量让大家都能看懂.这是上篇文章的连接http:// ...

  3. linux上编写运行 dotnet core api

    安装 Ubuntu        dotnet core 跨平台已不再是梦,它带来的意义非凡,比如api接口可以在linux上编写及部署,也可以在windows上编写好,打包发布,然后copy到lin ...

  4. 国产中标麒麟Linux部署dotnet core 环境并运行项目 (三) 部署运行WEB API项目

    部署dotnet Core Web API 上一步的文章,是我们公司最核心的一个ORM组件,在中标麒麟系统完成了一个插入数据的任务,这一步是将正式的从dot net framework 迁移到 dot ...

  5. Gitlab CI 自动部署 asp.net core web api 到Docker容器

    为什么要写这个? 在一个系统长大的过程中会经历不断重构升级来满足商业的需求,而一个严谨的商业系统需要高效.稳定.可扩展,有时候还不得不考虑成本的问题.我希望能找到比较完整的开源解决方案来解决持续集成. ...

  6. 手把手教你使用spring cloud+dotnet core搭建微服务架构:服务治理(-)

    背景 公司去年开始使用dotnet core开发项目.公司的总体架构采用的是微服务,那时候由于对微服务的理解并不是太深,加上各种组件的不成熟,只是把项目的各个功能通过业务层面拆分,然后通过nginx代 ...

  7. Dapr Pub/Sub 集成 RabbitMQ 、Golang、Java、DotNet Core

    前置条件: <Dapr运用> <Dapr 运用之 Java gRPC 调用篇> <Dapr 运用之集成 Asp.Net Core Grpc 调用篇> 搭建 Rabb ...

  8. 国产中标麒麟Linux部署dotnet core 环境并运行项目 (二) 部署运行控制台项目

    背景 在上一篇文章安装dotnet core,已经安装好dotnet core了.之前只是安装成功了dotnet, 输入dotnet --info,可以确认安装成功了,但是在运行代码时,还是报错了,本 ...

  9. 使用 dotnet core 和 Azure PaaS服务进行devOps开发(Web API 实例)

    作者:陈希章 发表于 2017年12月19日 引子 这一篇文章将用一个完整的实例,给大家介绍如何基于dotnet core(微软.NET的最新版本,支持跨平台,跨设备的应用开发,详情请参考 https ...

随机推荐

  1. EF Code 如何输出sql语句

    首先写拷贝下面类 public class EFLoggerProvider : ILoggerProvider { public ILogger CreateLogger(string catego ...

  2. 用windbg查看dmp文件,定位bug位置

    windbg + .dmp + .pdb + 源代码,可以看到是哪个代码崩溃的 设置符号文件所在路径 File->Symbol File Path... 在输入框中填入.pdb文件所在的文件夹路 ...

  3. HBase面试考点

    HBase 架构图 组成部分及作用 Zookeeper在HBase中作用 Master的高可用 RegionServer的监控 元数据的入口 HMaster 不仅有维护集群元数据信息的功能,还能 通过 ...

  4. sqlite 显示表内容时乱码,无法正常显示汉字,

    把txt文件另存为时,选择编码为utf-8即可

  5. Redis系列(九):Redis的事务机制

    提到事务,相信大家都不陌生,事务的ACID四大特性,也是面试时经常问的,不过一般情况下,我们可能想到的是传统关系型数据库的事务,其实,Redis也是提供了事务机制的,本篇博客就来讲解下Redis的事务 ...

  6. 机器学习笔记簿 降维篇 PCA 01

    降维是机器学习中十分重要的部分,降维就是通过一个特定的映射(可以是线性的或非线性的)将高维数据转换为低维数据,从而达到一些特定的效果,所以降维算法最重要的就是找到这一个映射.主成分分析(Princip ...

  7. PHP date_sub() 函数

    ------------恢复内容开始------------ 实例 从 2013 年 3 月 15 日减去 40 天: <?php$date=date_create("2013-03- ...

  8. PHP filetype() 函数

    定义和用法 filetype() 函数返回指定文件或目录的类型. 如果成功,该函数返回 7 种可能的值之一.如果失败,则返回 FALSE. 可能的返回值: fifo char dir block li ...

  9. PHP is_resource() 函数

    is_resource() 函数用于检测变量是否为资源类型. PHP 版本要求: PHP 4, P+-HP 5, PHP 7高佣联盟 www.cgewang.com 语法 bool is_resour ...

  10. PHP log10() 函数

    实例 返回不同数的以 10 为底的对数: <?phpecho(log10(2.7183) . "<br>");echo(log10(2) . "< ...