如何限制每个客户端的WCF服务

我正在开发一项服务,该服务将在互联网上暴露给少数精选客户。 但是,我不希望一个客户端能够经常调用服务,以防止另一个客户端调用该服务,或者在合理的时间内响应。 我意识到WCF内置了许多限制配置设置,但据我所知,这些设置仅适用于整个服务。

是否有任何内置机制可以使我配置服务,使单个客户端只能执行(例如)10个并发呼叫或类似的?

这个问题与另一个问题有关:

几乎没有客户端在互联网上保护WCF服务的最佳方法

我还在努力确定是否需要,以及如何识别个人客户。

你在这里寻找的短语是速率限制 。 而且,不,没有内置的方法来限制WCF服务。 如您所说,您可以使用围绕服务限制的WCFfunction集,但这是服务级别设置而不是每个客户端。

为了实现速率限制,一般指导似乎是使用内存中的集合(或类似于用于横向扩展方案的redis)来对传入的用户字符串或IP地址执行快速查找。 然后,您可以围绕该信息定义一些限制算法。

更多信息在这里和这里 。

首先,如果您使用某种负载均衡器,最好在那里实施。 例如,NGINX具有速率限制function: http ://nginx.org/en/docs/http/ngx_http_limit_req_module.html。

其次,您应该考虑使用称为动态IP限制的内置IIS速率限制function: http : //www.iis.net/learn/get-started/whats-new-in-iis-8/iis-80-dynamic-ip-地址限制 。

如果这两个对你来说都不够,因为你需要自定义逻辑,你总是可以在应用程序级别实现它。 这可以通过多种方式完成。

让我们从一些可重用的速率限制逻辑开始:

public interface IRateLimiter { bool ShouldLimit(string key); HttpStatusCode LimitStatusCode { get; } } public interface IRateLimiterConfiguration { int Treshhold { get; set; } TimeSpan TimePeriod { get; set; } HttpStatusCode LimitStatusCode { get; set; } } public class RateLimiterConfiguration : System.Configuration.ConfigurationSection, IRateLimiterConfiguration { private const string TimePeriodConst = "timePeriod"; private const string LimitStatusCodeConst = "limitStatusCode"; private const string TreshholdConst = "treshhold"; private const string RateLimiterTypeConst = "rateLimiterType"; [ConfigurationProperty(TreshholdConst, IsRequired = true, DefaultValue = 10)] public int Treshhold { get { return (int)this[TreshholdConst]; } set { this[TreshholdConst] = value; } } [ConfigurationProperty(TimePeriodConst, IsRequired = true)] [TypeConverter(typeof(TimeSpanConverter))] public TimeSpan TimePeriod { get { return (TimeSpan)this[TimePeriodConst]; } set { this[TimePeriodConst] = value; } } [ConfigurationProperty(LimitStatusCodeConst, IsRequired = false, DefaultValue = HttpStatusCode.Forbidden)] public HttpStatusCode LimitStatusCode { get { return (HttpStatusCode)this[LimitStatusCodeConst]; } set { this[LimitStatusCodeConst] = value; } } [ConfigurationProperty(RateLimiterTypeConst, IsRequired = true)] [TypeConverter(typeof(TypeNameConverter))] public Type RateLimiterType { get { return (Type)this[RateLimiterTypeConst]; } set { this[RateLimiterTypeConst] = value; } } } public class RateLimiter : IRateLimiter { private readonly IRateLimiterConfiguration _configuration; private static readonly MemoryCache MemoryCache = MemoryCache.Default; public RateLimiter(IRateLimiterConfiguration configuration) { _configuration = configuration; } public virtual bool ShouldLimit(string key) { if (!string.IsNullOrEmpty(key)) { Counter counter = new Counter {Count = 1}; counter = MemoryCache.AddOrGetExisting(key, new Counter { Count = 1 }, DateTimeOffset.Now.Add(_configuration.TimePeriod)) as Counter ?? counter; lock (counter.LockObject) { if (counter.Count < _configuration.Treshhold) { counter.Count++; } else { return true; } } } return false; } public HttpStatusCode LimitStatusCode { get { return _configuration.LimitStatusCode; } } private class Counter { public volatile int Count; public readonly object LockObject = new object(); } } public class RateLimiterFactory { public IRateLimiter CreateRateLimiter() { var configuration = GetConfiguration(); return (IRateLimiter)Activator.CreateInstance(configuration.RateLimiterType, configuration); } public static RateLimiterConfiguration GetConfiguration() { return ConfigurationManager.GetSection("rateLimiter") as RateLimiterConfiguration ?? new RateLimiterConfiguration(); } } static class GetClientIpExtensions { private const string XForwardedForHeaderName = "X-Forwarded-For"; private const string HttpXForwardedForServerVariableName = "HTTP_X_FORWARDED_FOR"; private const string HttpRemoteAddressServerVariableName = "REMOTE_ADDR"; public static string GetClientIp(this Message message) { return GetClientIp(message.Properties); } public static string GetClientIp(this OperationContext context) { return GetClientIp(context.IncomingMessageProperties); } public static string GetClientIp(this MessageProperties messageProperties) { var endpointLoadBalancer = messageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty; if (endpointLoadBalancer != null && endpointLoadBalancer.Headers[XForwardedForHeaderName] != null) { return endpointLoadBalancer.Headers[XForwardedForHeaderName]; } else { var endpointProperty = messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty; return (endpointProperty == null) ? string.Empty : endpointProperty.Address; } } public static string GetClientIp(this HttpRequest request) { string ipList = request.ServerVariables[HttpXForwardedForServerVariableName]; return !string.IsNullOrEmpty(ipList) ? ipList.Split(',')[0] : request.ServerVariables[HttpRemoteAddressServerVariableName]; } } 

这使用配置,使用接口和默认MemoryCache进行适当的隔离。 您可以轻松更改实现以抽象缓存。 这将允许使用不同的缓存提供程序,例如redis。 如果您希望为运行相同服务的多个服务器提供分布式缓存,这可能很有用。

现在以此代码为基础,我们可以使用它添加一些实现。 我们可以添加一个IHttpModule:

 public class RateLimiterHttpModule : IHttpModule { private readonly IRateLimiter _rateLimiter; public RateLimiterHttpModule() { _rateLimiter = new RateLimiterFactory().CreateRateLimiter(); } public void Init(HttpApplication context) { context.BeginRequest += OnBeginRequest; } private void OnBeginRequest(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; string ip = application.Context.Request.GetClientIp(); if (_rateLimiter.ShouldLimit(ip)) { TerminateRequest(application.Context.Response); } } private void TerminateRequest(HttpResponse httpResponse) { httpResponse.StatusCode = (int)_rateLimiter.LimitStatusCode; httpResponse.SuppressContent = true; httpResponse.End(); } public void Dispose() { } } 

或者只适用于任何传输级别的WCF实现:

 public class RateLimiterDispatchMessageInspector : IDispatchMessageInspector { private readonly IRateLimiter _rateLimiter; public RateLimiterDispatchMessageInspector(IRateLimiter rateLimiter) { _rateLimiter = rateLimiter; } public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { if (_rateLimiter.ShouldLimit(request.GetClientIp())) { request = null; return _rateLimiter.LimitStatusCode; } return null; } public void BeforeSendReply(ref Message reply, object correlationState) { if (correlationState is HttpStatusCode) { HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty(); reply.Properties["httpResponse"] = responseProperty; responseProperty.StatusCode = (HttpStatusCode)correlationState; } } } public class RateLimiterServiceBehavior : IServiceBehavior { public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection endpoints, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { var rateLimiterFactory = new RateLimiterFactory(); foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers) { foreach (EndpointDispatcher epDisp in chDisp.Endpoints) { epDisp.DispatchRuntime.MessageInspectors.Add(new RateLimiterDispatchMessageInspector(rateLimiterFactory.CreateRateLimiter())); } } } } public class RateLimiterBehaviorExtensionElement : BehaviorExtensionElement { protected override object CreateBehavior() { return new RateLimiterServiceBehavior(); } public override Type BehaviorType { get { return typeof(RateLimiterServiceBehavior); } } } 

您可以类似地为ASP.NET MCV执行操作筛选器。 在这里查看: 如何在ASP.NET MVC站点中实现速率限制? 。

您可以像这样更改配置:

  ConcurrencyMode:=ConcurrencyMode.Single InstanceContextMode:=InstanceContextMode.Single 

然后,在代码中,设置两个服务级变量:

  • 一个字符串变量,用于保存最后一个请求者的ID
  • 一个整数变量用于访问次数。

对于每个请求,其中入站用户的ID ==最后保存的用户,请将整数变量增加+1。 在请求10之后,向用户返回拒绝。 如果用户不同,则重置变量并处理请求。

它不是配置解决方案 – 它的配置和代码,但它可以工作。