繁体   English   中英

ASP.NET身份电话号码令牌生命周期和短信限制

[英]ASP.NET Identity Phone Number Token lifespan and SMS limit

我正在使用ASP.NET Identity 2.0构建2因子注册API。
我想让用户能够根据需要确认他们的电话号码,所以即使他们在注册时没有确认他们是电话号码,他们总是可以请求通过短信发送的新令牌(向我的API提出请求)并在页面上输入(也向我的API发出请求)。
在负责发送令牌的方法中,我正在生成令牌并发送它,如下所示:

var token = await UserManager.GeneratePhoneConfirmationTokenAsync(user.Id);
var message = new SmsMessage
{
    Id = token,
    Recipient = user.PhoneNumber,
    Body = string.Format("Your token: {0}", token)
};
await UserManager.SmsService.SendAsync(message);

在UserManager中:

public virtual async Task<string> GeneratePhoneConfirmationTokenAsync(TKey userId)
{
    var number = await GetPhoneNumberAsync(userId);
    return await GenerateChangePhoneNumberTokenAsync(userId, number);
}

每次我调用我的方法我都会收到包含令牌的短信,问题是用户可以无限次地调用该metod并且很容易产生费用 - 每次短信=费用。

我想将用户可以对该方法执行的请求数限制为每X分钟一次。

另外我注意到,当我做多个请求时,我得到相同的令牌,我已经测试了我的方法,它看起来这个令牌有效3分钟,所以如果我在那个分钟时间窗口请求我将获得相同的令牌。

理想情况下,我希望有一个参数,允许我指定请求和电话确认令牌寿命之间的时间间隔。

我尝试使用以下方法在UserManager类中设置令牌生命周期:

appUserManager.UserTokenProvider = new DataProtectorTokenProvider<User,int>(dataProtectionProvider.Create("ASP.NET Identity"))
{
    TokenLifespan = new TimeSpan(0,2,0)//2 minutes 
};

但这只影响电子邮件确认链接中的令牌。

我是否需要在我的用户表中添加额外的字段,该字段将保存令牌有效日期并在每次我想生成和发送新令牌时检查它还是更方便?

如何指定ASP.NET标识生成相同电话号码确认令牌的时间间隔?

我不是专家,但我有同样的问题,并在谷歌的帮助下发现这两个线程。

https://forums.asp.net/t/2001843.aspx?Identity+2+0+Two+factor+authentication+using+both+email+and+sms+timeout

https://github.com/aspnet/Identity/issues/465

我将根据AspNet Identity github讨论,假设您的默认时间限制是3分钟。

希望链接的讨论包含配置新时间限制所需的答案。

关于速率限制,我使用以下代码,这些代码基于此讨论, 如何在ASP.NET MVC站点中实现速率限制?

class RateLimitCacheEntry
{
    public int RequestsLeft;

    public DateTime ExpirationDate;
}

/// <summary>
/// Partially based on
/// https://stackoverflow.com/questions/3082084/how-do-i-implement-rate-limiting-in-an-asp-net-mvc-site
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class RateLimitAttribute : ActionFilterAttribute
{
    private static Logger Log = LogManager.GetCurrentClassLogger();

    /// <summary>
    /// Window to monitor <see cref="RequestCount"/>
    /// </summary>
    public int Seconds { get; set; }

    /// <summary>
    /// Maximum amount of requests to allow within the given window of <see cref="Seconds"/>
    /// </summary>
    public int RequestCount { get; set; }

    /// <summary>
    /// ctor
    /// </summary>
    public RateLimitAttribute(int s, int r)
    {
        Seconds = s;
        RequestCount = r;
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        try
        {
            var clientIP = RequestHelper.GetClientIp(actionContext.Request);

            // Using the IP Address here as part of the key but you could modify
            // and use the username if you are going to limit only authenticated users
            // filterContext.HttpContext.User.Identity.Name
            var key = string.Format("{0}-{1}-{2}",
                actionContext.ActionDescriptor.ControllerDescriptor.ControllerName,
                actionContext.ActionDescriptor.ActionName,
                clientIP
            );

            var allowExecute = false;

            var cacheEntry = (RateLimitCacheEntry)HttpRuntime.Cache[key];

            if (cacheEntry == null)
            {
                var expirationDate = DateTime.Now.AddSeconds(Seconds);

                HttpRuntime.Cache.Add(key,
                    new RateLimitCacheEntry
                    {
                        ExpirationDate = expirationDate,
                        RequestsLeft = RequestCount,
                    },
                    null,
                    expirationDate,
                    Cache.NoSlidingExpiration,
                    CacheItemPriority.Low,
                    null);

                allowExecute = true;
            }
            else
            {
                // Allow and decrement
                if (cacheEntry.RequestsLeft > 0)
                {
                    HttpRuntime.Cache.Insert(key,
                        new RateLimitCacheEntry
                        {
                            ExpirationDate = cacheEntry.ExpirationDate,
                            RequestsLeft = cacheEntry.RequestsLeft - 1,
                        },
                        null,
                        cacheEntry.ExpirationDate,
                        Cache.NoSlidingExpiration,
                        CacheItemPriority.Low,
                        null);

                    allowExecute = true;
                }
            }

            if (!allowExecute)
            {
                Log.Error("RateLimited request from " + clientIP + " to " + actionContext.Request.RequestUri);

                actionContext.Response
                    = actionContext.Request.CreateResponse(
                        (HttpStatusCode)429,
                        string.Format("You can call this {0} time[s] every {1} seconds", RequestCount, Seconds)
                    );
            }
        }
        catch(Exception ex)
        {
            Log.Error(ex, "Error in filter attribute");

            throw;
        }
    }
}

public static class RequestHelper
{
    /// <summary>
    /// Retrieves the client ip address from request
    /// </summary>
    public static string GetClientIp(HttpRequestMessage request)
    {
        if (request.Properties.ContainsKey("MS_HttpContext"))
        {
            return ((HttpContextWrapper)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
        }

        if (request.Properties.ContainsKey(RemoteEndpointMessageProperty.Name))
        {
            RemoteEndpointMessageProperty prop;
            prop = (RemoteEndpointMessageProperty)request.Properties[RemoteEndpointMessageProperty.Name];
            return prop.Address;
        }

        return null;
    }
}

我也看过这个库推荐了几次: https//github.com/stefanprodan/WebApiThrottle

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM