2016-10-05 25 views
1

我正在使用ASP.NET Identity 2.0構建2個因子註冊API。
我想讓用戶能夠按需確認他們的電話號碼,所以即使他們在註冊時沒有確認他們是電話號碼,他們總是可以請求新的令牌(向我的API發送請求)通過短信並在頁面上輸入(也向我的API請求)。如下圖所示
在方法,是負責發送令牌我生成令牌並將其發送:ASP.NET身份電話號碼令牌生命週期和SMS限制

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無限次數並輕鬆地生成成本 - 每個SMS =成本。

我想將用戶可以對該方法執行的請求數量限制爲每X分鐘一次。

另外我注意到,當我做多個請求時,我得到相同的令牌,我測試了我的方法,它看起來這個令牌有效3分鐘,所以如果我在那個分鐘的時間窗口請求,我會得到同樣的道理。

理想情況下,我想讓單個參數能夠指定請求和電話確認令牌使用壽命之間的時間間隔。

我已經嘗試使用設置的UserManager類的內部令牌的使用壽命:

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

但這隻影響電子郵件確認鏈接標記。

我是否需要爲我的用戶表添加額外的字段來保存令牌有效日期,並在每次我想要生成併發送新令牌時檢查它,還是有更簡單的方法?

如何指定ASP.NET身份將生成相同電話號碼確認標記的時間間隔?

+0

如果我米沒有誤差2分鐘,它應該是TimeSpan(0,2,0)而不是TimeSpan(0,0,2),這是2秒。 –

+0

@DovMiller很好,我馬上解決。 – Misiu

回答

1

我不是專家,但我有同樣的問題,發現這兩個線程與谷歌的一點幫助。

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

我會假設你是正確的,默認的時間限制是3分鐘基礎上,ASPNET身份github上討論。

希望鏈接的討論包含您需要配置新時間限制的答案。

關於速率限制我使用的是鬆散的基礎上討論這個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