Mis*_*siu 7 c# asp.net asp.net-web-api2 asp.net-identity-2
我正在使用 ASP.NET Identity 2.0 构建 2 因子注册 API。
我想让用户能够按需确认他们的电话号码,所以即使他们在注册时没有确认他们的电话号码,他们也总是可以请求将通过 SMS 发送的新令牌(向我的 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);
Run Code Online (Sandbox Code Playgroud)
和内部用户管理器:
public virtual async Task<string> GeneratePhoneConfirmationTokenAsync(TKey userId)
{
var number = await GetPhoneNumberAsync(userId);
return await GenerateChangePhoneNumberTokenAsync(userId, number);
}
Run Code Online (Sandbox Code Playgroud)
每次我调用我的方法时,我都会收到包含令牌的 SMS 消息,问题是用户可以无限次调用该方法,并且很容易产生成本 - 每条 SMS = 成本。
我想将用户可以对该方法执行的请求数量限制为每 X 分钟一次。
我还注意到,当我执行多个请求时,我得到了相同的令牌,我已经测试了我的方法,看起来这个令牌在 3 分钟内有效,所以如果我在那个分钟的时间窗口内请求,我将得到相同的令牌。
理想情况下,我希望有一个参数可以让我指定请求和电话确认令牌寿命之间的时间间隔。
我尝试使用以下方法在 UserManager 类中设置令牌寿命:
appUserManager.UserTokenProvider = new DataProtectorTokenProvider<User,int>(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = new TimeSpan(0,2,0)//2 minutes
};
Run Code Online (Sandbox Code Playgroud)
但这只会影响电子邮件确认链接中的令牌。
我是否需要在我的用户表中添加额外的字段来保存令牌有效日期并在每次我想生成和发送新令牌时检查它,或者有更简单的方法吗?
如何指定 ASP.NET Identity 将生成相同电话号码确认令牌的时间间隔?
我不是专家,但我有同样的问题,并在谷歌的帮助下找到了这两个线程。
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
/// /sf/ask/215745911/
/// </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;
}
}
Run Code Online (Sandbox Code Playgroud)
我也看过几次推荐这个库:https : //github.com/stefanprodan/WebApiThrottle