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

时间:2016-10-05 10:38:12

标签: c# asp.net asp.net-web-api2 asp.net-identity-2

我正在使用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标识生成相同电话号码确认令牌的时间间隔?

1 个答案:

答案 0 :(得分:3)

我没有专家,但我有同样的问题,并在谷歌的帮助下找到了这两个主题。

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分钟。

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

关于速率限制我使用以下代码,这些代码基于此讨论How do I implement rate limiting in an ASP.NET MVC site?

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