Windows Communication Foundation (WCF) is een nieuwe technologie voor het bouwen van servicegeoriënteerde applicaties. Het kan onder andere gebruikt worden voor het opzetten van een Service Oriented Architecture (SOA), maar ook voor simpelere scenario’s van communicatie tussen twee applicaties op dezelfde machine tot processen op verschillende machines. In dit artikel beschrijven de auteurs een aantal ‘best practices’ en wat je moet overwegen wanneer je WCF gaat toepassen.
WCF kan worden toegepast wanneer je twee verschillende stukken code met elkaar wilt laten communiceren. Daarbij kun je aan verschillende scenario’s denken. Een mogelijk scenario is interoperabiliteit, waarbij een servicegeoriënteerde applicatie gemaakt kan worden die over het internet communiceert met andere services, geschreven in een totaal andere technologie zoals Java. Een ander mogelijk scenario op bescheidener schaal kan zijn wanneer je twee klasse in dezelfde assembly in hetzelfde proces met elkaar wilt laten praten.
Samengevat kun je WCF gebruiken voor elk nieuw stuk code waar je over de grens van processen heen wilt of wanneer je binnen hetzelfde proces wilt verbinden met ontkoppelde objecten (zoals services). Je kunt hierbij het bestaan van .NET remoting, ASP.NET Web Services en Enterprise Services vergeten en je richten op WCF voor alle mogelijke communicatiebehoeften.
Voor het bouwen van WCF services is een aantal best practices van toepassing. Met toepassing van deze practices kan men effectieve, veilig en betrouwbare services bouwen. In dit artikel zullen zeven practices worden toegelicht:
- Gelaagdheid
- Instantiëring van aanroep
- Omgaan met fouten
- Kiezen van de juiste service host
- Verantwoorde manier van callbacks
- Gebruikmaken van onderhoudbare proxy code
- Beveiliging
Voordat we ingaan op de best practices leggen we kort uit hoe communicatie binnen WCF werkt.
Server
Verbinden van twee stukken code met WCF vereist dat je eerst een aantal zaken regelt aan de server kant. Ten eerste heb je een servicecontract nodig. Deze definieert de operaties die je wilt aanbieden via de service, samen met de data die er doorheen loopt. Vervolgens zijn datacontracten nodig voor complexe types die door de service operaties heen gaan. Deze contracten bepalen de structuur van de verzonden data, zodat deze data kan worden geconsumeerd of geproduceerd door de client. Daaropvolgend heb je een service implementatie nodig. Hier bevindt zich functionele code, die de vraag beantwoordt van binnenkomende berichten en wat er mee moet gebeuren. Naast implementatie, service- en datacontract is een configuratie van de service vereist. Binnen de configuratie wordt gespecificeerd hoe de service wordt blootgesteld in termen als address, binding en contract (het abc). De binding bijvoorbeeld bevat informatie over:
- welk netwerkprotocol gebruikt gaat worden;
- codering van het bericht;
- eventuele beveiligingsmechanismes die worden toegepast en of gebruikt gemaakt wordt van betrouwbaarheid (reliability);
- transacties of andere mogelijkheden die WCF ondersteunt.
Ten slotte is een host nodig om de service te kunnen laten werken. De zogenaamde service host kan een .NET proces zijn, dat zelf gehost wordt in bijvoorbeeld een Windows console applicatie, IIS, Windows Activation Service (WAS) voor Vista en Windows Server 2008.
Client
Aan de kant van de client, de service-consument, zijn drie zaken nodig om met WCF een service aanroep te kunnen uitvoeren, waarbij het niet uitmaakt of de service met WCF of met een andere technologie is geïmplementeerd:
- Een service-contract definitie, die overeenkomt met wat aan de server kant gebruikt wordt;
- Een data-contract definitie overeenkomstig met wat aan de server kant gebruikt wordt;
- Een proxy om berichten te maken om naar de service te versturen en antwoordberichten te verwerken. Figuur 1 geeft de situatie weer tussen de service aan de server kant en de client als consument in twee scenario’s zoals eerder als voorbeeld aangegeven.

Fig. 1: Communicatie tussen service en client
Scheiding in lagen
Gelaagdheid is een belangrijk principe binnen ontwikkelen van software. Het scheiden van functionaliteit in presentatielaag, businesslaag, en data(toegangs)laag is al jaren bekend. Het opdelen in lagen levert scheiding op van verantwoordelijkheden, makkelijker te onderhouden code en betere schaalbaarheid bij fysieke scheiding van de lagen. In de datatoegangslaag, beter bekend als Data Access Layer (DAL), ligt de verantwoordelijkheid van de vertaling tussen de database en het applicatiedomein. Een dergelijke verantwoordelijkheid is ook nodig in een aparte service laag die zich richt op vertaling tussen applicatiedomein en de servicegeoriënteerde buitenwereld (zie figuur 2).
Gelaagdheid is een belangrijk principe binnen ontwikkelen van software

Fig. 2: Scheiding in lagen
Een servicelaag impliceert dat de servicedefinitie zich in een aparte klassenbibliotheek bevindt, welke wordt gehost in een host omgeving zoals IIS. Binnen de servicelaag worden aanroepen doorgezet naar de businesslaag om de gevraagde operatie te verwerken. WCF maakt het mogelijk je servicecontract en operatiecontract attributen direct in je serviceklasse te stoppen. Dit is echter iets wat je moet zien te voorkomen. Het is beter de interfacedefinitie, die duidelijk definieert wat je service boundary is, af te zonderen van de implementatie van de service. Voor een simpele service ziet je contractdefinitie eruit zoals in listing 1.
[ServiceContract()]
public interface IProductService
{
[OperationContract()]
List<ConsumerProduct> GetProducts();
}
Listing 1
Een belangrijk aspect van serviceoriëntatie is het verbergen van alle details van je implementatie achter een service boundary. Dit geldt eveneens voor de technologie die je gebruikt voor de implementatie. Daarbij is het niet wenselijk dat je aanneemt dat de applicatie een complex datamodel ondersteunt. Een deel van je service boundary definitie is de datacontractdefinitie voor (complexe) types, welke als operatieparameters kunnen worden doorgegeven aan of kunnen worden teruggestuurd door de service Voor maximale interoperabiliteit en aansluiting met principes van serviceoriëntatie zul je niet specifiek .NET types, zoals DataSets of excepties, moeten versturen buiten je service boundary. Je moet je houden aan simpele datastructuurobjecten zoals klassen met properties en achterliggende member-velden. Je kunt objecten laten passeren die geneste complexe types bevatten, zoals klant en order collecties. Je mag daarbij echter niet aannemen dat een klant objectgeoriënteerde constructies als interfaces of basisklassen ondersteunt ten behoeve van interoperabiliteit met webservices, al dan niet geïmplementeerd met een andere technologie.
Als je WCF alleen als nieuwe manier van remoting technologie wilt toepassen om twee stukken .NET code met elkaar te laten communiceren over een proces met de verwachting of eis voor andere om consumerende applicaties te schrijven, dan kun je alle .NET types gebruiken in je datacontract. Je zult er voor moeten zorgen dat dergelijke types wel gemarkeerd zijn als datacontracten of te serialiseren zijn. Eigenlijk sta je voor diverse uitdagingen wanneer je datasets door WCF laat passeren, wat je het beste kunt vermijden (behalve in zeer eenvoudige scenario’s). Als je datasets wilt toepassen in WCF, werk dan met getypeerde datasets waarbij je moet trachten zelf getypeerde datasets als parameters of terugkerende waarde te gebruiken. Zoals eerder beschreven bevat een simpel contract een type als: List<CustomerProduct>. WCF is ontworpen om enumerable collecties 'plat te slaan' in arrays op de service boundary. In plaats van het verminderen van interoperabiliteit maakt dit je leven eenvoudiger wanneer je collecties vult en gebruikt in de service zelf en in de businesslagen. Kijk eens naar de datacontract definitie voor het CustomerProductType in listing 2.
[DataContract()]
public class ConsumerProduct
{
private int productID;
private string productName;
private double unitPrice;
[DataMember()]
public int ProductID
{
get { return productID; }
set { productID = value; }
}
[DataMember()]
public string ProductName
{
get { return productName; }
set { productName = value; }
}
[DataMember()]
public double UnitPrice
{
get { return unitPrice; }
set { unitPrice = value; }
}
}
Listing 2
Enkele instantie per aanroep
Een best practice is een instantie per aanroep in te stellen als default. WCF ondersteunt drie instantiemogelijkheden voor services, namelijk:
- Per-Call;
- Per-Session;
- Single.
Per-Call houdt in dat een nieuwe instantie van een serviceklasse wordt gecreëerd voor elke aanroep die de client doet van een operatie. Deze instantie wordt opgeruimd wanneer deze afgerond is. Dit is de meest schaalbare en robuuste optie.
Per-session betekent dat client de service-instantie gaande houdt op de server zolang de client aanroepen blijft doen aan de service. Hiermee kun je de state van een service vasthouden in member-variabelen van de service-instantie. Je bent daardoor in staat stateful conversatie tussen client-applicatie en server-object te doen. Dit heeft echter wel een aantal neveneffecten, waaronder geheugengebruik op de server, waarmee de schaalbaarheid wordt beïnvloed.
Ten slotte stelt de Single(ton) mogelijkheid je in staat aanroepen aan de service van alle clients te routeren naar een enkele instantie van de service op de server. Hierdoor verkrijg je men één enkel punt als zogenaamde 'poortwachter'. Deze mogelijkheid is slecht voor de schaalbaarheid, omdat alle aanroepen in de singleton instantie geserialiseerd worden (één voor één).
Singleton routeert alle calls naar een enkele instantie van de service
Single en Per-Session delen vergelijkbare neveneffecten, zoals schaalbaarheid. De beste optie voor ontwerp van services is Per-Call, omdat het de schoonste, veiligste en meest schaalbare optie is. De andere opties hebben alleen nut wanneer je extra mogelijkheden nodig hebt van deze specifieke opties, zoals een stateful mechanisme. De code in listing 3 declareert een Per-Call service.
[ServiceBehavior(
InstanceContextMode=InstanceContextMode.PerCall)]
public class ProductService : IProductService
{
// Code
}
Listing 3
Omgaan met fouten
Afhandeling van fouten is altijd al een belangrijk onderwerp geweest in het maken van applicaties. Dit geldt ook voor het maken van services met behulp van WCF. Wanneer een niet afgehandelde fout een service boundary bereikt, zal WCF deze opvangen en een SOAP fout retourneren naar de aanroepende partij. Standaard is deze fout nietszeggend en geeft deze geen details over het precieze probleem en de oorzaak ervan. Op zich is hier niets mis mee: het komt overeen met de ontwerpprincipes van serviceoriëntatie. Standaard toon je alleen die informatie aan de consument van een service die je wilt tonen. Je wilt voorkomen dat details als stack-traces, die normaliter bij foutafhandeling voorkomen, worden getoond.
De architectuur van WCF is dusdanig dat je deze in twee lagen kunt opsplitsen, namelijk de channellaag en servicemodellaag. De channellaag is verantwoordelijk voor de communicatie-infrastructuur en de servicemodellaag voor een objectgeoriënteerd en declaratief programmeermodel. Figuur 3 geeft beide WCF architectuurlagen weer.

Fig. 3: Architectuur van WCF
De channellaag is volledig te configureren en uit te breiden. De binding is hierop van toepassing. Een niet afgehandelde exceptie zal in de channellaag resulteren in een fout, nog voordat de service zelf bereikt wordt. Dit houdt in dat je een volgende aanroep van de client niet door dezelfde proxy kunt laten verlopen. Er zal een nieuwe connectie naar de server moeten worden gemaakt. Dit heeft tot gevolg dat je een goede servicelaag zult moeten ontwerpen. Deze servicelaag handelt alle fouten af en gooit een FaultException<T> exceptie. Dit geldt in alle gevallen wanneer een service van een exceptie kan herstellen en volgende aanroepen zonder problemen kan afhandelen.
De FaultException<T> is een speciaal .NET type dat zich binnen de .NET call stack gedraagt als een normale exceptie. Deze wordt echter anders geïnterpreteerd door de WCF laag die de messaging verzorgt. Je kunt het beschouwen als een afgehandelde exceptie, die naar WCF wordt aangeleverd in plaats van een normale exceptie. Deze exception propageert zichzelf in WCF zonder interventie van de service. Je kunt gedetailleerde gegevens achterwege laten richting de client over de reden van het probleem door gebruik te maken van de Reason property van het FaultException<T> type. Als een FaultException<T> wordt opgevangen door WCF, zal deze worden verpakt als een SOAP fault message, maar binnen het channel zal er geen fout plaatsvinden. Dit betekent dat je de client kan instrueren de opgetreden fout af te handelen en de service kan blijven benaderen zonder een nieuwe connectie op te zetten.
Er zijn veel verschillende scenario’s denkbaar voor het afhandelen van excepties in WCF alsmede voor de manier waarop je de foutafhandeling in de servicelaag wilt regelen. De volgende basisregels moet je altijd hanteren:
- Vang alle niet afgehandelde excepties in WCF af en werp een FaultException<T> op als je service in staat is te herstellen en volgende calls zonder problemen kan afhandelen (dit moet mogelijk zijn als je instantie per aanroep in stelt (Per-Call);
- Stuur geen exceptiedetails naar de client behalve voor debugging doeleinden tijdens ontwikkeling;
- Stuur geen gedetailleerde informatie naar client gebruikmakend van de reason Property van de FaultException <T>.
Listing 4 laat zien hoe een servicemethode een exceptie afvangt. Daarbij wordt geen gedetailleerde informatie van het opgetreden probleem naar de client gestuurd middels de FaultException <T>:
List<ConsumerProduct> IProductService.GetProducts()
{
try
{
ProductManager prodMgr = new ProductManager();
return
ConsumerProductEntityTransformer.
GetConsumerProducts(
prodMgr.GetProductDetailList());
}
catch (SqlException ex)
{
throw new FaultException<string>(
“The service could not connect to the data store”,
// detailinformatie,
“Unknown error”
// reden);
}
}
Listing 4
De generic type parameter T kan elk gewenste type zijn, maar in het kader van interoperabiliteit wil je het liefst een simpele datastructuur gemarkeerd als datacontract of als types die te serializeren zijn.
Kies de juiste service host
WCF kent drie opties voor hosten van een service. Men kan zelf de service hosten (draaien van je services in elke gewenste .NET applicatie), hosten binnen IIS of gebruik maken van Windows Activiation Services (WAS).
Zelf hosten binnen een applicatie (self-hosting) geeft je de meeste flexibiliteit, omdat je zelf de hosting-omgeving opzet. Dit betekent dat je volledig toegang hebt tot je hosting-omgeving en dat je de configuratie zelf kunt coderen. Ook kun je zaken zoals operatiemonitoring en andere controles aanhaken aan de service door middel van events. Het zelf hosten van de service legt echter ook de verantwoordelijkheid van procesmanagement en andere configuratie mogelijkheden bij de bouwer. Listing 5 beschrijft een dergelijk scenario van self-hosting:
public partial class MyServiceHost : Servicebase
{
ServiceHost productServiceHost = null;
public MyServiceHost()
{
InitializeComponent();
}
protected override void OnStrart(string args[])
{
productServiceHost = new
ServiceHost(typeof(ProductService));
productServiceHost.Open();
}
protected override void OnStop()
{
if(productServiceHost != null)
productServiceHost.Close();
}
}
Listing 5
Hosten in IIS stelt je in staat je services uit te rollen in IIS door simpel weg de DLL te plaatsen in de \BIN folder en de .SVC files als service adresseerbare endpoints te definiëren. Hiermee verkrijg je op kernelniveau request routering van IIS, de IIS management consoles voor configureren van de hosting-omgeving, de IIS mogelijkheid van starten en hergebruiken van werk processen, en nog veel meer. Het grote nadeel van hosten in IIS (tot en met IIS 6) is dat je beperkt bent tot HTTP gebaseerde bindings. In de toekomst vervalt deze beperking met IIS 7.0 en WAS.
WAS is de beste keuze om nieuwere platformen te ondersteunen
WAS is een onderdeel van IIS 7 (Windows Vista en Windows Server 2008) en het geeft je een hosting-model waarbij je jouw services kunt blootstellen (aanbieden) met andere protocollen dan HTTP, zoals TCP, Named Pipes en MSMQ. WAS is altijd de beste keuze als je nieuwere platformen gaat ondersteunen. Als je services buiten je intranet gaat aanbieden, zul je over het algemeen HTTP gaan gebruiken, dus IIS is het beste voor hosten van externe services. Als WAS hosting geen optie is voor draaien van services intern op het intranet, dan kun je overwegen zelf de services te hosten. Dit heeft als voordeel dat andere (snellere en meer capabele) protocollen binnen je firewall kunt gebruiken zodat het je meer flexibiliteit geeft ten aanzien van configuratie van je eigen omgeving.
Verantwoord gebruik van callbacks
WCF bevat een eigenschap om een callback te doen naar een client om asynchroon data te routeren of als een vorm van eventsignalering. Dit is handig wanneer je aan de client kenbaar wilt maken dat een lang lopende operatie klaar is in bijvoorbeeld je backend, of dat je een client op de hoogte wilt brengen van verandering in data die gevolgen kan hebben voor de client. Om dit voor elkaar te krijgen moet je een callback-contract definiëren dat is gekoppeld (overeenkomt met) aan je servicecontract. De client hoeft het callback-contract niet publiekelijk te maken aan de buitenwereld als een service, maar de service kan het gebruiken om een callback te doen naar de client nadat de initiële aanroep reeds is gedaan vanuit de client naar service. Listing 6 creëert een servicecontract-definitie met daaraan gekoppeld een callback-contract.
[ServiceBehavior(
InstanceContextMode=InstanceContextMode.PerCall)]
public class ProductService : IProductService
{
#region IProductService Members
List<ConsumerProduct> IProductService.GetProducts()
{
try
{
ProductManager prodMgr = new ProductManager();
return ConsumerProductEntityTransformer.
GetConsumerProducts(
prodMgr.GetProductDetailList());
}
catch (SqlException ex)
{
throw new FaultException<string>(
“Service could not connect to the data store”,
“Unknown error”);
}
}
#endregion
}
Listing 6
Als je van plan bent callbacks te gebruiken, is het een goede overweging het aan te bieden via een publish/subscribe API als onderdeel van het servicecontract. Voor uitvoeren van een callback zal de service de context van een binnenkomende aanroep moeten opvangen en vasthouden in geheugen tot het moment dat een aanroep terug heeft plaatsgevonden naar de client. De client zal een object moeten aanmaken, dat een callback-interface implementeert om binnenkomende aanroepen van de service te kunnen ontvangen. De client zal vervolgens dit object en haar proxy levend moeten houden zolang callbacks verwacht kunnen worden. Dit is een sterk gekoppeld communicatiemechanisme tussen de client en de service (inclusief objectafhankelijkheden), dus is het een goed idee om de client de controle te verschaffen over wanneer de koppeling start en eindigt door middel van expliciete serviceaanroepen die aanvangen en eindigen bij een conversatie. Grootste limiterende factor van callback is het niet kunnen schalen en mogelijk niet werken in interoperabiliteit-scenario’s. Het probleem met schalen is gerelateerd aan het feit dat de service een referentie moet hebben naar de client die in geheugen wordt vastgehouden om een callback te kunnen uitvoeren op de referentie. Listing 7 illustreert hoe je client-notificatie kunt opvangen, opslaan en veranderen door middel van een callback-referentie voor een service.
class ProductServiceWithCallbacks : IProductService2
{
static List<IProductServiceCallback> subscribers =
new List<IProductServiceCallback>();
#region IProductService2 Members
List<Product.Entities.ConsumerProduct>
IProductService2.GetProducts()
{
throw new NotImplementedException();
}
void IProductService2.SubscribeProductChanges()
{
IProductServiceCallback client =
OperationContext.Current.
GetCallbackChannel<IProductServiceCallback>();
lock (subscribers)
{
subscribers.Add(client);
}
}
void IProductService2.UnsubscribeProductChanges()
{
IProductServiceCallback client =
OperationContext.Current.
GetCallbackChannel<IProductServiceCallback>();
lock (subscribers)
{
subscribers.Remove(client);
}
}
#endregion
static void NotifyClientsProductChanged
(ConsumerProduct product)
{
lock (subscribers)
{
foreach (IProductServiceCallback client
in subscribers)
{
client.ProductChanged(product);
}
}
}
}
Listing 7
Je zult interoperabiliteitproblemen tegenkomen omdat je callback-mechanisme is gebaseerd op Microsoft WCF technologie, en die is niet standaard. Het is een vendor-specifieke standaard, maar bepaalde zaken zijn uitgedrukt binnen een SOAP-bericht dat geconsumeerd of gebruikt kan worden door andere technologieën. Dus als interoperabiliteit een deel van de wensen en eisen van de klant zijn, kun je callback beter vermijden. Een alternatief kan zijn om een polling-API op te zetten waar een client komt vragen om veranderingen op bepaalde tijdstippen, of je kunt een publish/subscribe middleman service opzetten als broker subscriptions en publicaties om koppeling tussen client en service te voorkomen zodat deze wel te schalen zijn.
Gebruikmaken van onderhoudbare proxy code
Een van de principes van serviceoriëntatie is dat je schema’s en contracten deelt: geen types. Het je zo min mogelijk afhankelijk maken van de client van de servicedefinitie is daarbij noodzakelijk. Geen types buiten de service en de client die geen deel uitmaken van de service boundary. Echter, wanneer je zowel de service als de client schrijft, wil je niet twee typedefinities voor hetzelfde maken.
Een van de principes van serviceoriëntatie is dat je schema’s en contracten deelt: geen types.
Het kan geen kwaad aan de clientzijde een referentie aan te leggen met een assembly die ook door de service wordt gebruikt om toegang te krijgen tot .NET typedefinities van service- en data-contracten. Je moet ervoor zorgen dat de service bruikbaar is voor de client als ze niet beide toegang hebben tot een gedeelde assembly; dat is beter dan het hergenereren van dergelijke types aan de clientzijde op basis van metadata. Om dit te doen definieer je de types in een aparte assembly van de service-implementatie, zodat zowel de client als service een referentie kunnen leggen zonder additionele koppeling. Als je dit doet introduceer je meer afhankelijkheid tussen client en service, maar dan vanwege productiviteit en snelheid op gebied van ontwikkeling en onderhoud.
Zoals eerder bij het stukje over het omgaan met fouten is beschreven, kan een niet afgehandelde fout in een channel ook een fout opleveren. Dit geldt voor de meeste bindings. Wanneer een fout wordt geleverd aan WCF client, wordt er een FaultException gegenereerd als het niet als FaultException<T> op de service zijde is geïntroduceerd. Dit is echter niet voor elke binding consequent zo geregeld. Je wilt dat je service- en client-code zijn ontkoppeld, onafhankelijk van je binding. Het enige veilige dat je aan de client kant kunt doen is uitgaan van het een worst-case scenario als de service een exceptie gooit. In deze situatie vermijd je dat de proxy opnieuw wordt gebruikt. Het weggooien of sluiten van de proxy kan resulteren in een volgende exceptie. Dit betekent dat je de calls naar een service in een try/catch blok moet zetten en de proxy-instantie moet vervangen met een nieuwe in het catch blok, zoals te zien is in listing 8.
public class MyClient
{
ProductServiceClient proxy =
new ProductServiceClient();
public Window1()
{
InitializeComponent();
}
private void OnGetProducts(
object sender, RoutedEventArgs e)
{
try
{
DataContext = proxy.GetProducts();
}
catch (Exception ex)
{
proxy = new ProductServiceClient();
}
}
}
Listing 8
Beveiliging
Tot slot moet beveiliging rond WCF niet vergeten worden. Beveiliging van je applicatie en dus ook van een service is belangrijk om oneigenlijk gebruik of het vrijkomen van gevoelige data te voorkomen.
In dit artikel gaan we alleen in op de beveiliging van TCP en SOAP verkeer. Hierbij speelt een aantal WCF-bindings een rol, waarvan de meest gebruikte zijn: NetTcpBinding (intranet applicaties), BasicHTTPBinding en WSHTTPBinding (internet applicaties).
De beveiliging van een WCF service kan worden onderverdeeld in twee soorten beveiliging, namelijk het beschermen van berichten tegen ongewenste meelezers (transportbeveiliging en berichtbeveiliging) en het voorkomen dat berichten meer dan eens worden aangeboden (beveiliging tegen replay-attacks).
De beveiliging van een WCF applicatie gebeurt altijd per binding. Beschikbare beveiligingsopties verschillen per binding; een overzicht hiervan is te vinden op de volgende locatie: http://msdn.microsoft.com/en-us/library/ms731092.aspx.
Transportbeveiliging en berichtbeveiliging
Beveiliging van dataverkeer is mogelijk door te kiezen voor transportbeveiliging, berichtbeveiliging of een combinatie van beide. Transportbeveiliging is in de meeste gevallen efficiënter dan berichtbeveiliging. Transportbeveiliging heeft echter te maken met het risico dat tussenstations voor het verkeer deze beveiliging kunnen verbreken. Berichtbeveiliging is minder efficiënt, maar biedt wel ‘end-to-end’ beveiliging. De keuze voor één van beide technieken is sterk verbonden met de omgeving waarin het dataverkeer plaats vindt.
Intranet applicaties
Bij applicaties die op een intranet worden geïnstalleerd, is het verstandig om te kiezen voor een TCP binding met transportbeveiliging. Ten eerste is binair transport over TCP significant sneller dan SOAP gebaseerd transport over HTTP. Dit is onder andere te danken aan de mogelijkheid om zonder IIS een verbinding te kunnen opzetten. Bij een intranet applicatie is er veel controle over eventuele tussenstations in de verbinding, waardoor het risico op verbreking van de beveiliging zeer klein is. De NetTcpBinding voldoet standaard aan deze manier van datatransport.
In listing 9 wordt een door middel van transportbeveiliging beveiligde NetTcpBinding uitgewerkt. Om een beveiliging uit te werken dient altijd een security-sectie onder de binding-sectie te worden aangemaakt, waarin de beveiligingsmodus wordt weergegeven. In deze sectie moet de gekozen beveiliging door een subsectie worden uitgewerkt.
<netTcpBinding>
<binding name=”netTcp”>
<security mode=”Transport”>
<transport clientCredentialType=”Windows” />
</security>
</binding>
</netTcpBinding>
Listing 9
Internet applicaties
Applicaties die dataverkeer hebben dat over het internet wordt geleid, hebben weinig baat bij transportbeveiliging. Als een tussenstation de beveiliging wil verbreken, dan is daarop geen enkele controle. Het ligt daarom voor de hand om te kiezen voor berichtbeveiliging. Omdat het versturen van binaire data over het internet niet mogelijk is, ligt het voor de hand om te werken met SOAP over HTTP. Het hosten van een internet applicatie in IIS heeft als extra voordeel dat het beveiligingsmechanisme van IIS kan worden gebruikt. Ook is IIS sterk in het beheren van services en in het bieden van schaalbaarheid.
Als data erg gevoelig is, is het raadzaam om naast berichtbeveiliging ook te kiezen voor transportbeveiliging. Hoewel transportbeveiliging over het internet geen sluitende beveiliging biedt, maakt het de job van een hacker een stuk minder aantrekkelijk. Houdt goed in de gaten dat transportbeveiliging voor dataverkeer over het internet nooit berichtbeveiliging zal kunnen vervangen.
Als er een connectie moet worden gelegd met verouderde (legacy) clients, zoals clients die ASMX web services verwachten, dan is er geen andere optie dan te kiezen voor BasicHTTPBinding. Omdat de beveiligingsopties van de WS* serie (bekend van WSE 3.0) ontbreken, is dit de minst te beveiligen binding. Het is daarom nodig dat er goed wordt nagegaan of clients de WS* beveiligingsopties ondersteunen, zodat kan worden gekozen voor de WSHTTPBinding. Let er wel op dat de WSHTTPBinding standaard is geconfigureerd op berichtbeveiliging, terwijl verouderde clients met WS* ondersteuning in een aantal gevallen (veel niet-Microsoft clients) enkel transportbeveiliging ondersteunen.
Om een binding aan te maken met berichtbeveiliging, moet de sectie security worden aangemaakt met als modus Message, waarin een message-sectie moet worden aangemaakt. In listing10 is hiervan een voorbeeld uitgewerkt, waarbij ook het clientCredentialType, de manier waarop de client zich verifieert bij de service, is gewijzigd van de standaard modus Windows naar verificatie met een certificaat.
<wsHttpBinding>
<binding name=”wsHTTP”>
<security mode=”Message”>
<message clientCredentialType=”Certificate” />
</security>
</binding>
</wsHttpBinding>
Listing 10
Beveiliging tegen replay-attacks
Als een WCF-service op de juiste manier is beveiligd met transportbeveiliging of berichtbeveiliging, wordt veelal vergeten dat er een risico is dat een onderschept bericht meermalen wordt aangeboden aan de service. In veel gevallen zal dit niet direct heftige problemen opleveren, maar in de volgende situaties is het van groot belang om te zorgen voor een beveiliging tegen deze zogenaamde replay-attacks:
- Een opnieuw aangeboden bericht veroorzaakt inconsistentie van data. Denk hierbij bijvoorbeeld aan het verwerken van betalingsgegevens;
- Een bericht wordt gerouteerd via onbetrouwbare tussenstations. Dit is eigenlijk altijd het geval bij berichtenverkeer via het internet;
- Verwerking van berichten is relatief gezien het meest intensieve proces van de applicatie. In deze situatie kan een hacker zeer eenvoudig zorgen voor een bottleneck binnen de service en zo een ‘denial of service’ realiseren.
Uit bovenstaande situaties blijkt dat het niet nodig is interne applicaties, bijvoorbeeld op een intranet, te voorzien van beveiliging tegen replay-attacks. In vrijwel alle gevallen waarbij het internet in het spel verschijnt, is het raadzaam om je tegen replay-attacks te beveiligen.
Het is raadzaam om je tegen replay-attacks te beveiligen
Het principe achter de beveiliging tegen replay-attacks is vrij eenvoudig en verloopt zoals te zien is in Figuur 4.

Fig. 4: Beveiliging tegen replay-attacks
De client-applicatie ondertekent het bericht met een unieke code. De service controleert het verzonden bericht aan de hand van deze unieke code. Dit vindt plaats door middel van een replay-cache waarin gedurende een bepaalde termijn binnengekomen codes worden opgeslagen. Wordt de code gevonden in de cache, dan wordt het bericht afgewezen, anders worden code en timestamp in de cache opgeslagen.
Om in WCF gebruik te maken van beveiliging tegen replay-attacks, moet een custom binding worden aangemaakt. Hierin maak je een security-element aan. Vervolgens kan met een aantal properties de beveiliging worden geregeld. Beschikbare properties zijn:
- detectReplays: deze boolean property moet aan worden gezet om de beveiliging te laten functioneren. Dit is de standaardwaarde;
- maxClockSkew: geeft het tijdsverschil aan dat wordt getolereerd tussen client en server. Standaard staat deze property ingesteld op vijf minuten, maar de beveiliging wordt strenger als deze tijd wordt ingekort;
- replayWindow: hiermeey wordt ingesteld hoe lang een bericht wordt beschouwd als ‘gevaarlijk’ met betrekking tot replaying. Berichten die binnen deze tijdspan vallen worden gecontroleerd;
- replayCacheSize: het aantal unieke codes dat door de applicatie tegelijkertijd kan worden bijgehouden. Als het aantal berichten verzonden binnen de replayWindow groter is dan de cachegrootte, worden berichten geweigerd tot de cache weer ruimte heeft voor nieuwe unieke codes. Standaard kan de cache 500.000 berichten herbergen.
Een listing van het instellen van beveiliging tegen replay-attacks vanuit de configuratie is afgedrukt in listing 11.
<customBinding>
<binding name=”NewBinding”>
<textMessageEncoding />
<security>
<localClientSettings
replayCacheSize=”800000”
maxClockSkew=”00:03:00”
replayWindow=”00:03:00” />
<localServiceSettings
replayCacheSize=”800000”
maxClockSkew=”00:03:00”
replayWindow=”00:03:00” />
<secureConversationBootstrap />
</security>
<HttpTransport />
</binding>
</customBinding>
Listing 11
Tot slot
Met deze best practices tonen we aan dat het niet extreem ingewikkeld is om WCF in een applicatie te integreren. Hopelijk is aan de hand van dit artikel duidelijk geworden welke voordelen WCF biedt ten opzichte van het huidige .NET 2.0 framework en hoe je deze technologie het best kunt toepassen. De beste manier om de inhoud van dit artikel te gebruiken is om de beschreven best practices te doorgronden en ze toe te passen in je persoonlijke 'speeltuin'.