Popular blog tags

Introduction

In this article, we will learn how to implement Token Based Authentication in Web APIs to secure the data.

There are 4 common methods of Web API Authentication:

HTTP Authentication Schemes (Basic & Bearer)
API Keys
OAuth (2.0)
OpenID Connect

Here we will learn OAuth authentication. OAuth is an open standard for token based authentication and authorization on internet. By using OAuth we can create Token Based Authentication API.

常用认证方式

一般基于REST API 安全设计常用方式有:

 

Microsoft.AspNetCore.OData

https://github.com/grandnode/grandnode/tree/develop/Grand.Api

 

JWT

 

IdentityServer4认证方式

 

2. Web Api 安全

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请求的请求引用头进行检查,可以防止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>();
    ...
}