简体   繁体   中英

WCF - Multiple Endpoints for REST and SOAP

I am trying to code a WCF service with both REST and SOAP endpoints. I was initially using "TransportCredentialOnly" for the SOAP endpoints. As I started to add REST endpoints...I am using a third party OAUTH 1.0 class to provide security to the REST Service.

With the "TransportCredentialOnly" authentication, I had to enable "Windows Authentication" on the IIS Website Application.

The issue I am having is that the REST calls come back with a "Authentication failed" as IIS is expecting an initial authentication to happen with Windows Credentials before hitting the REST Endpoint.

I enabled "Anonymous Authentication" on the IIS application but still be prompted for Windows Credentials before proceeding.

Is there anyway to keep the "Windows Authentication" scheme for SOAP calls and have Anonymous authentication on the REST endpoints (which will proceed to use OAuth 1.0)? I don't really want to separate this out into two projects/services as some of the functions/methods/classes are universal between the SOAP and REST calls.

Here is my web config so far of attempts:

<system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5"/>
    <authentication mode="Windows"/>
    <authorization>
      <allow roles="DOMAIN\Security_Group"/>
      <deny users="*"/>
    </authorization>
    <customErrors mode="Off"/>
  </system.web>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpEndpointBinding">
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Windows"/>
          </security>
        </binding>
      </basicHttpBinding>
      <webHttpBinding>
            <binding name="webHttpBindingWithJsonP" />
      </webHttpBinding>
    </bindings>
    <standardEndpoints>
      <webHttpEndpoint>
        <standardEndpoint name="Anonymous">
            <security mode="None"/>
        </standardEndpoint>
      </webHttpEndpoint>
    </standardEndpoints>
    <services>
       <service name="service name">      
        <endpoint address="SOAP"
                  binding="basicHttpBinding"
                  bindingConfiguration="BasicHttpEndpointBinding"
                  contract="contract name">
          <identity>
             <dns value="localhost" />
          </identity>
        </endpoint>        
        <endpoint address="REST"
                  kind="webHttpEndpoint"
                  binding ="webHttpBinding"
                  bindingConfiguration="webHttpBindingWithJsonP"
                  endpointConfiguration="Anonymous"
                  behaviorConfiguration="restBehavior"
                  contract ="contract name">
        </endpoint>   
        <host>
          <baseAddresses>
            <add baseAddress="service url"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="restBehavior">
          <webHttp helpEnabled="true" />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <useRequestHeadersForMetadataAddress/>
        </behavior>    
      </serviceBehaviors>
    </behaviors>
    <!--<protocolMapping>
      <add binding="basicHttpsBinding" scheme="http" />
    </protocolMapping>-->
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="false" />
  </system.serviceModel>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <!--<add name="Access-Control-Allow-Origin" value="*"/>
        <add name="Access-Control-Allow-Methods" value="POST,GET"/>
        <add name="Access-Control-Allow-Headers" value="Content-Type, Accept"/>-->
      </customHeaders>
    </httpProtocol>
    <modules runAllManagedModulesForAllRequests="true"/>
    <directoryBrowse enabled="true"/>
  </system.webServer>
</configuration>

10-20-2015 Update : I applied a new config to use serviceAuthorization in the web.config file

<service name="service name"  behaviorConfiguration="Oauth">
<endpoint address="service address"               
          binding ="webHttpBinding"
          behaviorConfiguration="restBehavior"
          contract ="service contract">
</endpoint>
<host>
  <baseAddresses>
    <add baseAddress="base address"/>
  </baseAddresses>
</host>
</service>

<behavior name="Oauth">
  <serviceMetadata httpGetEnabled="true" />
  <serviceDebug includeExceptionDetailInFaults="false" />
  <serviceAuthorization serviceAuthorizationManagerType="Assembly.OAuthAuthorizationManager,Assembly" />
</behavior>

Here is the OAuthorizationManager Class:

 public class OAuthAuthorizationManager : ServiceAuthorizationManager
{
    protected override bool CheckAccessCore(OperationContext operationContext)
    {
        bool Authenticated = false;

        string normalizedUrl;
        string normalizedRequestParameters;

        base.CheckAccessCore(operationContext);

        // to get the httpmethod

        HttpRequestMessageProperty requestProperty = (HttpRequestMessageProperty)(operationContext.RequestContext.RequestMessage).Properties[HttpRequestMessageProperty.Name];

        string httpmethod = requestProperty.Method;

        // HttpContext.Current is null, so forget about it 
        // HttpContext context = HttpContext.Current; 

        NameValueCollection pa = HttpUtility.ParseQueryString(operationContext.IncomingMessageProperties.Via.Query);

        if (pa != null && pa["oauth_consumer_key"] != null)
        {
            // to get uri without oauth parameters
            string uri = operationContext.IncomingMessageProperties
            .Via.OriginalString.Replace
               (operationContext.IncomingMessageProperties
            .Via.Query, "");

            string consumersecret = "secret";

            OAuthBase oauth = new OAuthBase();

            string hash = oauth.GenerateSignature(
                new Uri(uri),
                pa["oauth_consumer_key"],
                consumersecret,
                null, // totken
                null, //token secret
                httpmethod,
                pa["oauth_timestamp"],
                pa["oauth_nonce"],
                out normalizedUrl,
                out normalizedRequestParameters
                );
            Authenticated = pa["oauth_signature"] == hash;
        }
        return Authenticated;
    }
}

There is a workable solution to have a REST based WCF service hosted in IIS can use its own custom Basic Authentication. You can easily send back a response header to challenge for Basic Authentication credentials, and just have IIS wired up to "Anonymous Authentication." In this case IIS will only take care of Hosting and that's it.

Create a custom Authorization manager class that inherits from ServiceAuthorizationManager and configure this to your service.

public class RestAuthorizationManager : ServiceAuthorizationManager
{
    protected override bool CheckAccessCore(OperationContext operationContext)
    {
        //Extract the Authorization header, and parse out the credentials converting the Base64 string:
        var authHeader = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
        if ((authHeader != null) && (authHeader != string.Empty))
        {
            var svcCredentials = System.Text.ASCIIEncoding.ASCII
                    .GetString(Convert.FromBase64String(authHeader.Substring(6)))
                    .Split(':');
            var user = new { Name = svcCredentials[0], Password = svcCredentials[1] };
            if ((user.Name == "user1" && user.Password == "test"))
            {
                //User is authrized and originating call will proceed
                return true;
            }
            else
            {
                //not authorized
                return false;
            }
        }
        else
        {
            //No authorization header was provided, so challenge the client to provide before proceeding:
            WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"MyWCFService\"");
            //Throw an exception with the associated HTTP status code equivalent to HTTP status 401
            throw new WebFaultException(HttpStatusCode.Unauthorized);
        }
    }
}

Add the above custom authorization manager to configuration:

<serviceBehaviors>
  <behavior name="SecureRESTSvcTestBehavior">
    <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
<serviceAuthorization serviceAuthorizationManagerType="WcfRestAuthentication.Services.Api.RestAuthorizationManager, WcfRestAuthentication"/>
  </behavior>
</serviceBehaviors>

Apply above behavior to only REST End points. Now IIS won't have control of any authorization here anymore. Make sure you use SSL certificate to secure the creds from being sent as plain text.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM