Search code examples
wcfadapterbiztalksaprfc

SAP authentication with WCF Adapter Service project


I've been experimenting with the BizTalk Adapter Pack, specifically with the SAP stuff.

I'm able to connect to SAP and run an RFC from a client app (e.g. Console, Windows, Web site) using the SAP binding directly, no problem at all. I've also got it all working in BizTalk Server 2009 using orchestration, send port, receive port, etc.

However, I want to expose SAP functionality to internal users as a standard HTTP web service, without requiring them to have the SAP binding stuff installed on their PC.

So, I created a "WCF Adapter Service" project in Visual Studio, and followed the wizard. I then created a standard web app for the client end, and added a proxy using "Add service reference". It all worked OK, in that it found the service and added the proxy code, but when I try to invoke the service I get the error SapErrorMessage=Incomplete logon data.

What I don't know how to do is pass SAP credentials from my client web app to the Basic HTTP service and then on the the SAP binding. If I put the SAP credentials into the SAP connection string it all works OK, but this is not very secure and also I want to supply the credentials from the client (i.e. ask the user to provide their SAP credentials).

In some examples I've seen, e.g. http://msdn.microsoft.com/en-us/library/dd788594(BTS.10).aspx, the SAP credentials are passed in HTTP headers. Unfortunately, all the examples I've seen of this go on to show how to invoke the service from SharePoint, where there's a dialog window for setting the headers. I'm not using SharePoint! I've tried adding a "" section to my client endpoint configuration, but this didn't seem to work.

So, what is the recommended way to pass SAP credentials to a Basic HTTP web service created via the "WCF Adapter Service" wizard?

For information, this is the relevant part of the web.config on my client web app:

<system.serviceModel>
    <bindings>
        <basicHttpBinding>
            <binding name="RfcEndpoint" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
                <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
                <security mode="None">
                    <transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
                    <message clientCredentialType="UserName" algorithmSuite="Default"/>
                </security>
            </binding>
        </basicHttpBinding>
    </bindings>
    <client>
        <endpoint address="http://xxxxxx/SAP_Service_1/Rfc.svc" binding="basicHttpBinding" bindingConfiguration="RfcEndpoint" contract="ServiceReference1.Rfc" name="RfcEndpoint">
            <headers>
                <SAP_Username>username</SAP_Username>
                <SAP_Password>password</SAP_Password>
            </headers>
        </endpoint>
    </client>
</system.serviceModel>

And this is the web.config generated by the WCF Adapter Service project wizard:

<?xml version="1.0"?>
<configuration>
    <system.serviceModel>
        <services>
            <service behaviorConfiguration="customServiceBehavior" name="RfcClient">
                <endpoint address="" behaviorConfiguration="customEndpointBehavior" binding="basicHttpBinding" bindingConfiguration="RfcClientBindingConfig" name="RfcEndpoint" contract="Rfc"/>
                <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
            </service>
        </services>
        <behaviors>
            <endpointBehaviors>
                <behavior name="customEndpointBehavior">
                    <endpointBehavior usernameHttpHeader="SAP_Username" passwordHttpHeader="SAP_Password" adapterSecurityBridgeType="HTTPUsernamePassword"/>
                </behavior>
            </endpointBehaviors>
            <serviceBehaviors>
                <behavior name="customServiceBehavior">
                    <serviceMetadata httpsGetEnabled="true"/>
                    <serviceDebug includeExceptionDetailInFaults="true"/>
                    <serviceCredentials type="Microsoft.ServiceModel.Channels.AdapterServiceCredentials, Microsoft.ServiceModel.Channels, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
                    <serviceAuthorization serviceAuthorizationManagerType="Microsoft.ServiceModel.Channels.AdapterServiceAuthorizationManager, Microsoft.ServiceModel.Channels, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
                        <authorizationPolicies>
                            <add policyType="Microsoft.ServiceModel.Channels.AdapterAuthorizationPolicy, Microsoft.ServiceModel.Channels, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
                        </authorizationPolicies>
                    </serviceAuthorization>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <extensions>
            <behaviorExtensions>
                <add name="endpointBehavior" type="Microsoft.ServiceModel.Channels.AdapterEndpointBehavior, Microsoft.ServiceModel.Channels, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
            </behaviorExtensions>
        </extensions>
        <bindings>
            <basicHttpBinding>
                <binding name="RfcClientBindingConfig">
                    <security mode="None">
                        <transport clientCredentialType="None"/>
                        <message clientCredentialType="UserName"/>
                    </security>
                </binding>
            </basicHttpBinding>
            <sapBinding>
                <binding name="SAPBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" enableBizTalkCompatibilityMode="false" receiveIdocFormat="Typed" enableSafeTyping="false" generateFlatFileCompatibleIdocSchema="true" maxConnectionsPerSystem="50" enableConnectionPooling="true" idleConnectionTimeout="00:15:00" flatFileSegmentIndicator="SegmentDefinition" enablePerformanceCounters="false" autoConfirmSentIdocs="false" acceptCredentialsInUri="true" padReceivedIdocWithSpaces="false" sncLibrary="" sncPartnerName="" rfcAllowStartProgram="">
                    <dataTypesBehavior datsMinToDateTime="NULL" datsMaxToDateTime="NULL" invalidDatsToDateTime="ERROR" emptyDatsToDateTime="0001-01-01T00:00:00" emptyTimsToDateTime="0001-01-01T00:00:00" dateTimeMaxToDats="99991231" dateTimeMinToDats="00010101" timsMaxToDateTime="NULL" invalidTimsToDateTime="ERROR" dateTimeMaxToTims="235959" dateTimeMinToTims="000000" invalidNumcToInt="0" emptyNumcToInt="0" dateTimeNullToDats="SKIP" dateTimeNullToTims="SKIP"/>
                </binding>
            </sapBinding>
        </bindings>
        <client>
            <endpoint address="sap://CLIENT=300;LANG=EN;@a/XXXXXX/00?RfcSdkTrace=False&amp;AbapDebug=False" binding="sapBinding" bindingConfiguration="SAPBinding" contract="Rfc" name="SAPBinding_Rfc"/>
        </client>
    </system.serviceModel>
</configuration>

I'm a bit new to WCF, so any help or pointers gratefully received!

Thanks

Doug


Solution

  • Found an even easier solution!

    WCF is happy to send the Username / Password credentials in the message body providing the message is protected. Don't know why I didn't think of it before, but setting security like this:

    <security mode="TransportWithMessageCredential">
    

    means that Rohit's suggestion now works.

    So, the service web.config expects the credentials in the message and uses transport security:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.serviceModel>
            <services>
                <service behaviorConfiguration="customServiceBehavior" name="RfcClient">
                    <endpoint address="" behaviorConfiguration="customEndpointBehavior"
                        binding="basicHttpBinding" bindingConfiguration="RfcClientBindingConfig"
                        name="RfcEndpoint" contract="Rfc" />
                    <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
                </service>
            </services>
            <behaviors>
                <endpointBehaviors>
                    <behavior name="customEndpointBehavior">
                        <endpointBehavior adapterSecurityBridgeType="ClientCredentialUsernamePassword" />
                    </behavior>
                </endpointBehaviors>
                <serviceBehaviors>
                    <behavior name="customServiceBehavior">
                        <serviceMetadata httpsGetEnabled="true" />
                        <serviceDebug includeExceptionDetailInFaults="true" />
                        <serviceCredentials type="Microsoft.ServiceModel.Channels.AdapterServiceCredentials, Microsoft.ServiceModel.Channels, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
                            <serviceCertificate findValue="servername" storeLocation="LocalMachine"
                                storeName="My" x509FindType="FindBySubjectName" />
                        </serviceCredentials>
                        <serviceAuthorization serviceAuthorizationManagerType="Microsoft.ServiceModel.Channels.AdapterServiceAuthorizationManager, Microsoft.ServiceModel.Channels, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
                            <authorizationPolicies>
                                <add policyType="Microsoft.ServiceModel.Channels.AdapterAuthorizationPolicy, Microsoft.ServiceModel.Channels, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
                            </authorizationPolicies>
                        </serviceAuthorization>
                    </behavior>
                </serviceBehaviors>
            </behaviors>
            <extensions>
                <behaviorExtensions>
                    <add name="endpointBehavior" type="Microsoft.ServiceModel.Channels.AdapterEndpointBehavior, Microsoft.ServiceModel.Channels, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
                </behaviorExtensions>
            </extensions>
            <bindings>
                <basicHttpBinding>
                    <binding name="RfcClientBindingConfig">
                        <security mode="TransportWithMessageCredential">
                            <transport clientCredentialType="None" />
                            <message clientCredentialType="UserName" />
                        </security>
                    </binding>
                </basicHttpBinding>
                <sapBinding>
                    <binding name="SAPBinding" closeTimeout="00:01:00" openTimeout="00:01:00"
                        receiveTimeout="00:10:00" sendTimeout="00:01:00" enableBizTalkCompatibilityMode="false"
                        receiveIdocFormat="Typed" enableSafeTyping="false" generateFlatFileCompatibleIdocSchema="true"
                        maxConnectionsPerSystem="50" enableConnectionPooling="true"
                        idleConnectionTimeout="00:15:00" flatFileSegmentIndicator="SegmentDefinition"
                        enablePerformanceCounters="false" autoConfirmSentIdocs="false"
                        acceptCredentialsInUri="false" padReceivedIdocWithSpaces="false"
                        sncLibrary="" sncPartnerName="" rfcAllowStartProgram="">
                        <dataTypesBehavior datsMinToDateTime="NULL" datsMaxToDateTime="NULL"
                            invalidDatsToDateTime="ERROR" emptyDatsToDateTime="0001-01-01T00:00:00"
                            emptyTimsToDateTime="0001-01-01T00:00:00" dateTimeMaxToDats="99991231"
                            dateTimeMinToDats="00010101" timsMaxToDateTime="NULL" invalidTimsToDateTime="ERROR"
                            dateTimeMaxToTims="235959" dateTimeMinToTims="000000" invalidNumcToInt="0"
                            emptyNumcToInt="0" dateTimeNullToDats="SKIP" dateTimeNullToTims="SKIP" />
                    </binding>
                </sapBinding>
            </bindings>
            <client>
                <endpoint address="sap://CLIENT=300;LANG=EN;@a/XXXXXX/00?RfcSdkTrace=False&amp;AbapDebug=False"
                    binding="sapBinding" bindingConfiguration="SAPBinding" contract="Rfc"
                    name="SAPBinding_Rfc" />
            </client>
        </system.serviceModel>
    </configuration>
    

    And the client web.config is similarly configured:

    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="RfcEndpoint" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
                    <security mode="TransportWithMessageCredential">
                        <transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
                        <message clientCredentialType="UserName" algorithmSuite="Default"/>
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://servername/SAP_Service_BASIC/Rfc.svc" binding="basicHttpBinding" bindingConfiguration="RfcEndpoint" contract="ServiceReference1.Rfc" name="RfcEndpoint"/>
        </client>
    </system.serviceModel>
    

    Then the client code is simply:

    string cust = "12345";
    string po = "12345";
    string salesOrg = "1234";
    string transGroup = "0";
    
    BAPIORDERS[] orders = new BAPIORDERS[0];
    BAPIRETURN ret = new BAPIRETURN();
    
    using (RfcClient client = new RfcClient("RfcEndpoint"))
    {
        client.ClientCredentials.UserName.UserName = "username";
        client.ClientCredentials.UserName.Password = "password";
    
        try
        {
            ret = client.BAPI_SALESORDER_GETLIST(cust, null, null, null, po, null, salesOrg, transGroup, ref orders);
    
            GridView1.DataSource = orders;
            GridView1.DataBind();
    
            Label1.Text = DateTime.Now.ToString();
    
            client.Close();
    
        }
        catch (CommunicationException ex)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(ex.Message);
            if (ex.InnerException != null) sb.Append("~" + ex.InnerException.Message);
            Label1.Text = sb.ToString();
            client.Abort();
        }
        catch (TimeoutException ex)
        {
            Label1.Text = ex.Message;
            client.Abort();
        }
        catch (Exception ex)
        {
            Label1.Text = ex.Message;
            client.Abort();
            throw;
        }
    
    }
    

    Perfect!