Archive

Posts Tagged ‘WCF’

Building a claims aware WCF Service using Windows Identity Foundation

February 18th, 2010 3 comments

Windows Identity Foundation (WIF) just shipped, so I decided to give it a go and see how easy it was to get started.
There’s an easy way to get started building an STS and claims aware WCF service, and there’s a hard way. The easy way is to just use the templates provided by Visual Studio. The hard way is to do everything manually. I decided to do it the hard way because of two reasons;
1: I wanted to learn the details, and 2: If I could get it working, I would WIF-enable existing projects (in my case, Silverlight projects).

So here’s the steps I went through in order to get a my basic claims aware WCF up and running:

1) In Visual Studio, create a new Web Application, let’s call it SecureWebApplication

2) Create an STS application (Security Token Service) by adding a new web application to the solution. Let’s call it SecureWebApplication_STS

3) Add a reference to Microsoft.IdentityModel.dll, System.IdentityModel.dll and System.ServiceModel.dll to the STS application.
Note! You may need to set Copy Local = true for Microsoft.IdentityModel.dll before the STS app can be called.

4) In the STS application, add the following classes: Common, AppSecurityTokenService, CertificateUtil and AppSecurityTokenServiceConfiguration. (Get implementation of classes from the web site you get when creating an ASP.NET Security Token Service Web Site. File -> New -> Web Site…).

5) Add a new text file to the STS application. Call it AppSTS.svc.

6) Edit AppSTS.svc so it contains the following line;

<%@ ServiceHost Language="C#" 
                Debug="true" 
                Factory="Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceHostFactory" 
                Service="SecureWebApplication_STS.AppSecurityTokenServiceConfiguration"  %>


This will enable our STS to be hosted by a service host in our web application.

7) In the STS application, edit the web.config file and add configuration settings that specifies how the STS should encrypt and sign issued security tokens;

<appSettings>
    <add key="IssuerName" value="SigninSTS"/>
    <add key="SigningCertificateName" value="CN=STSTestCert"/>
    <add key="EncryptingCertificateName" value="CN=STSTestCert"/>
  </appSettings>


Note! The STSTestCert is a X509Certificate installed as part of the Windows Identity Foundation SDK. Feel free to use a different certificate, but this walkthrough assumes that STSTestCert is used.

8 ) In the STS application, edit the web.config file and add the WCF service configuration for the STS service. 

<system.serviceModel>
    <services>
      <service name="Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract" 
               behaviorConfiguration="ServiceBehavior">
        <endpoint address="IWSTrust13" binding="ws2007HttpBinding" 
                  contract="Microsoft.IdentityModel.Protocols.WSTrust.IWSTrust13SyncContract" 
                  bindingConfiguration="ws2007HttpBindingConfiguration"/>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:14499/AppSTS.svc"/>
          </baseAddresses>
        </host>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <bindings>
      <ws2007HttpBinding>
        <binding name="ws2007HttpBindingConfiguration">
          <security mode="Message">
            <message establishSecurityContext="false"/>
          </security>
        </binding>
      </ws2007HttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>


9) We should now have a working STS that can be used by WCF Services. Let’s go back to your main web application and add a claims aware WCF Service.

10) Go to the main web application, SecureWebApplication, and add a reference to Microsoft.IdentityModel.dll and System.ServiceModel.dll.

Note! You may need to set Copy Local = true property for Microsoft.IdentityModel.dll before executing the app.

11) Add a Silverlight-enabled WCF Service, or just a plain WCF Service. I’ll be using a SL-enabled WCF Service in this walkthrough. Lets call the service MyService.scv.

12) Once you have added the WCF service, open the web.config-file

13) In the web.config-file, remove the default customBinding configuration added by Visual Studio and specify the service to use ws2007FederationHttpBinding instead.

Optionally, you can remove the <serviceHostingEnvironment aspNetCompatibilityEnabled=”True” /> element.

<services>
      <service behaviorConfiguration="SecureWebApplication.MyServiceBehavior"
               name="SecureWebApplication.MyService">
        <endpoint address="" binding="ws2007FederationHttpBinding"
                  bindingConfiguration="MyServiceBindingConfiguration"
                  contract="SecureWebApplication.MyService"/>
        <endpoint address="mex" binding="mexHttpBinding"
                  contract="IMetadataExchange"/>
      </service>
 </services>


14) Before we can configure the service behavior, we need to register a WCF extension for Windows Identity Foundation which allows federation.

In system.serviceModel, add an the following section

<extensions>
      <behaviorExtensions>
        <!-- This behavior extension will enable the service host to be Claims aware -->
        <add name="federatedServiceHostConfiguration" 
             type="Microsoft.IdentityModel.Configuration.ConfigureServiceHostBehaviorExtensionElement, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      </behaviorExtensions>
 </extensions>


15) Now we’re ready to define the service behavior. Add the following behavior to system.serviceModel/behaviors/serviceBehaviors. The service certificate we specify in the behavior is the certificate used by the STS to encrypt the security token (the certificate configured in step 7)

<behavior name="SecureWebApplication.MyServiceBehavior">
          <federatedServiceHostConfiguration/>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceCredentials>
            <serviceCertificate findValue="0E2A9EB75F1AFC321790407FA4B130E0E4E223E2" 
                                storeLocation="LocalMachine" storeName="My" 
                                x509FindType="FindByThumbprint"/>
          </serviceCredentials>
 </behavior>


16) Finally, we need to configure our ws2007FederationBinding, which is the binding used by out STS.

Add the following binding to system.serviceModel/bindings

<ws2007FederationHttpBinding>
        <binding name="MyServiceBindingConfiguration">
          <security mode="Message">
            <message>
              <issuerMetadata address="http://localhost:14499/AppSTS.svc/mex" />
            </message>
          </security>
        </binding>
      </ws2007FederationHttpBinding>


17) Our next steps involves configuring our WCF Service to use Windows Identity Foundation, so we’ll just stay in the web.config-file.

18) The first step is to register a new WIF config section so that we can specify our WIF configuration in the web.config-file. Add the following line to configuration/configSections in the web.config-file

<section name="microsoft.identityModel" 
             type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>


19) Now we’re ready to WIF-enable our WCF service. We do that by specifying the content of the microsoft.identityModel section as follows:

<microsoft.identityModel>
  <service>
    <audienceUris>
      <add value="http://localhost:14488/MyService.svc"/>
    </audienceUris>
    <federatedAuthentication>
      <wsFederation passiveRedirectEnabled="true" issuer="http://localhost:14499" realm="http://localhost:14488" requireHttps="false"/>
      <cookieHandler requireSsl="false"/>
    </federatedAuthentication>
    <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
      <trustedIssuers>
        <add thumbprint="0E2A9EB75F1AFC321790407FA4B130E0E4E223E2" name="http://localhost:14499"/>
      </trustedIssuers>
    </issuerNameRegistry>
  </service>
</microsoft.identityModel>


20) We now have a working claims aware WCF Service. To test it, you can create a new test web application in the same solution and add a service reference to the service.

WCF Routing Service and custom filters

January 19th, 2010 1 comment

WCF 4 now has a built-in way for routing WCF service calls, the System.ServiceModel.Routing.RoutingService, found in System.ServiceModel.Routing.dll. There are several reasons you may want to use routing, for example exposing just a single endpoint even though you have several services or routing messages throught multiple routers within a system.
The routing is done using message filters. Message filters are added to routing tables and specifies the endoints that the RoutingService should route messages to when the message filter condition is matched.

In this post, I’ll show an example of using custom message filters in order to route messages to two WCF services from a Silverlight application using the RoutingService.

1) Create a new Silverlight application and host it in a web site. Lets call it ServiceRouting.
2) Add a new Silverlight-enabled WCF Service to the web site, lets cal it DataService.svc
3) Open DataService.svc and set the Namespace property of the the ServiceContract attribute to some url, for example like this: 

[ServiceContract(Namespace = "http://services.company.com/myapp/processingservice")]

Note that setting the Namespace does not have anything to do with routing directly, but in this exaple I’ll be using Namespace in my custom filter to do the routing

4) Add a reference to System.ServiceModel.Routing.dll and add a routing service to your web site. To do this, simply add a new text file and call it RouterService.svc

Edit RouterService.svc so tha it looks like this.

<%@ ServiceHost Language="C#" Debug="true" Service="System.ServiceModel.Routing.RoutingService, 
     System.ServiceModel.Routing, version=4.0.0.0, Culture=neutral, 
     PublicKeyToken=31bf3856ad364e35" Factory="ServiceRouting.RoutingServiceHostFactory, ServiceRouting" %>

Note that I have specified a Factory. I’m using .NET 4.0 Beta 2 which requires using a service factory to add the

AspNetCompatibilityRequiremts attribute. The RoutingService in the release version of .NET 4 should contains that attrbute by default: Here is the factory class used to add the AspNetCompatibilityRequirements attribute. Add this class to your web site project.

public class RoutingServiceHostFactory : ServiceHostFactory
    {
        protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);
            AspNetCompatibilityRequirementsAttribute attrib = 
host.Description.Behaviors.Find<AspNetCompatibilityRequirementsAttribute>(); if (attrib == null) { attrib = new AspNetCompatibilityRequirementsAttribute(); host.Description.Behaviors.Add(attrib); } attrib.RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed; return host; } }

5) Next, we need to configure the routing service so that it can route messages to our Data Service.This is done in web.config.

a) Add the service configuration for the routing service

<services>
  <service behaviorConfiguration="routingServiceBehavior" name="System.ServiceModel.Routing.RoutingService">
    <endpoint address="" binding="basicHttpBinding" name="reqReplyEndpoint"
      contract="System.ServiceModel.Routing.IRequestReplyRouter" />
    <host>
      <baseAddresses>
        <add baseAddress="http://localhost:42001/RouterService.svc" />
      </baseAddresses>
    </host>
  </service>    
</services>


b) Add a service behavior for the routing service. The important part here is the routing element which specifies the routing table.

<behaviors>
  <serviceBehaviors>    
    <behavior name="routingServiceBehavior">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="true"/>
      <routing filterTableName="routingTable"/>
    </behavior>
  </serviceBehaviors>
</behaviors>

c) Add the routing table and message filters. These are the settings used by the router service to do the actual routing of the messages.

This is also the section where you specify your custom filter.

<system.serviceModel>  
  <routing>
    <filterTables>
      <filterTable name="routingTable">        
        <add filterName="dataServiceFilter" endpointName="DataServiceClient"/>
      </filterTable>
    </filterTables>
    <filters>      
      <filter name="dataServiceFilter"
              filterType="Custom"
              customType="ServiceRouting.CustomMessageFilter, ServiceRouting"
              filterData="http://services.company.com/myapp/dataservice"/>
    </filters>
  </routing>
</system.serviceModel>

d) Finally, we need to specify the endpoint to call when a message passes the message filter.

<system.serviceModel>
  <client>    
    <endpoint name="DataServiceClient"
              address="http://localhost:42001/DataService.svc"
              binding="basicHttpBinding"
              contract="*"/>
  </client>
</system.serviceModel>

Note! I have also changed the DataService configuration so that basicHttpBinding is used instead of the default
customBinding.
<service name="ServiceRouting.DataService">
        <endpoint address="" binding="basicHttpBinding" contract="ServiceRouting.DataService" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>

 

So now that we have our config and services in place, all we need to do is create the custom filter. To create a custom filter, you need to create a class that inherits from MessageFilter and has a constructor taking a string.

public class CustomMessageFilter : MessageFilter
    {
        private string _matchData;
        public CustomMessageFilter(string matchData)            
        {
            this._matchData = matchData;
        }        

        public override bool Match(System.ServiceModel.Channels.Message message)
        {
            return message.Headers.Action.IndexOf(this._matchData) > -1;            
        }

        public override bool Match(System.ServiceModel.Channels.MessageBuffer buffer)
        {
            throw new NotImplementedException();
        }
    }

The value specified in the filterData attribute is passed to CustomMessageFilter at creation time. The value can then be used to evaluate whether a message should pass the filter.

If there is a match, the DataServiceClient endpoint will be called.

To test the routing service, go to the Silverlight application and add a service reference to the DataService. Then in ServiceReferences.ClientConfig,  change the address to point to the routing service, http://localhost:42001/RouterService.svc.

Categories: WCF Tags: ,