2

我们目前有一个 WCF SOAP API,它允许消费者使用用户名和密码进行身份验证(内部使用UserNamePasswordValidator)作为参考,用户名和密码在 SOAP 正文中传递如下:

<o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" mustUnderstand="1">
<Timestamp Id="_0">
    <Created>
        2013-04-05T16:35:07.341Z</Created>
        <Expires>2013-04-05T16:40:07.341Z</Expires>
    </Timestamp>
    <o:UsernameToken Id="uuid-ac5ffd20-8137-4524-8ea9-3f4f55c0274c-12">
        <o:Username>someusername</o:Username>
        <o:Password o:Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">somepassword
    </o:Password>
</o:UsernameToken>
</o:Security>

我们希望额外支持消费者在HTTP 授权标头中指定凭据,作为基本身份验证或OAuth 持有者令牌

我们已经有几种方法可以实际对非 SOAP API 进行身份验证,但我不熟悉如何告诉 WCF 使用我可能为此创建的任何类。我怎样才能做到这一点?我看到的唯一一个试图回答这个问题的问题是here,但接受的答案使用 SOAP 标头,而不是 HTTP 标头,并且提问者基本上放弃了。

显然,任何解决方案都需要向后兼容——我们需要继续支持消费者在 SOAP 安全标头中指定凭据。

4

3 回答 3

2

您应该考虑实施ServiceAuthorizationManager为您的 WCF 服务实现一个来处理 HTTP Authorization 标头授权。

创建一个继承自 的类System.ServiceModel.ServiceAuthorizationManager,并覆盖一个或多个CheckAccess函数以检查传入的 Web 请求并决定是允许还是拒绝它。粗略的草图:

public class MyServiceAuthorizationManager: System.ServiceModel.ServiceAuthorizationManager
    {
        public override bool CheckAccess(OperationContext operationContext, ref Message message)
        {
            var reqProp = message.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
            var authHeader = reqProp.Headers[HttpRequestHeader.Authorization];

            var authorized = // decide if this message is authorized...

            if (!authorized)
            {
                var webContext = new WebOperationContext(operationContext);
                webContext.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
                webContext.OutgoingResponse.Headers.Add(HttpResponseHeader.WwwAuthenticate, String.Format("Bearer realm=\"{0}\"", baseUri.AbsoluteUri));
            }

            return authorized;
        }
}

将其连接到您创建服务主机的 WCF 服务中:

    restAPIServiceHost = new DataServiceHost(typeof(API.RestAPIService), restUris);

    var saz = restAPIServiceHost.Description.Behaviors.Find<ServiceAuthorizationBehavior>();
    if (saz == null)
    {
        saz = new ServiceAuthorizationBehavior();
        restAPIServiceHost.Description.Behaviors.Add(saz);
    }

    saz.ServiceAuthorizationManager = new MyServiceAuthorizationManager();

    restAPIServiceHost.Open();

这会将授权检查注入到 WCF 服务公开的每个方法中,而无需对服务方法本身进行任何更改。

您的 MyServiceAuthorizationManager 实现也可以使用 web.config 魔术安装到您的 WCF 服务中,但我发现直接代码更易于理解和调试。

请注意,很难在同一服务上使用多个授权检查系统,而不会相互影响或在您的安全范围内留下空白。如果您有UserNamePasswordValidator强制处理 SOAP 用户凭据的情况,它将拒绝仅包含 HTTP 授权标头的消息。同样,一个ServiceAuthorizationManager仅检查 HTTP Authorization 标头的 a 将使包含 SOAP 用户凭据的 Web 请求失败。您很可能需要弄清楚如何在同一个身份验证检查中检查这两种身份验证凭据表示。例如,如果消息中不存在 HTTP 授权标头,您可以向上面的 CheckAccess 函数添加代码以查找、提取和测试 SOAP 用户凭据。

当您必须接受多个身份验证表示时,您也需要决定优先级。如果存在 HTTP Authorization 标头,我怀疑它应该优先于 SOAP 消息中包含的任何内容。如果 HTTP 授权标头存在但无效,则句号 - 拒绝未授权的请求。SOAP 中的内容并不重要——无效的 HTTP 授权标头总是坏消息。如果根本没有 HTTP Authorization 标头,那么您可以四处寻找是否存在 SOAP 安全元素,您可以从中获取 SOAP 用户凭据并测试它们的有效性。

于 2013-04-14T05:47:46.750 回答
1

您可以采用的一种方法是使用MessageInspectors
像这样的东西:

首先- 创建消息检查器 - 负责使用您的凭据添加标题

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel;
using System.Xml;

namespace your_namespace
{


    /// <summary>
    /// /************************************
    /// * 
    /// * Creating Message inspector for 
    /// * updating all outgoing messages with Caller identifier header
    /// * read http://msdn.microsoft.com/en-us/magazine/cc163302.aspx
    /// * for more details
    /// * 
    /// *********************/
    /// </summary>
    public class CredentialsMessageInspector : IDispatchMessageInspector,
        IClientMessageInspector
    {
        public object AfterReceiveRequest(ref Message request,
            IClientChannel channel,
            InstanceContext instanceContext)
        {
            return null;
        }

        public void BeforeSendReply(ref Message reply, object
            correlationState)
        {
#if DEBUG
            //// Leave empty 
            //MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
            //Message message = buffer.CreateMessage();
            ////Assign a copy to the ref received
            //reply = buffer.CreateMessage();


            //StringWriter stringWriter = new StringWriter();
            //XmlTextWriter xmlTextWriter = new XmlTextWriter(stringWriter);
            //message.WriteMessage(xmlTextWriter);
            //xmlTextWriter.Flush();
            //xmlTextWriter.Close();

            //String messageContent = stringWriter.ToString();
#endif             
        }

        public void AfterReceiveReply(ref Message reply, object
            correlationState)
        {
#if DEBUG
            //// Leave empty 
            //MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
            //Message message = buffer.CreateMessage();
            ////Assign a copy to the ref received
            //reply = buffer.CreateMessage();


            //StringWriter stringWriter = new StringWriter();
            //XmlTextWriter xmlTextWriter = new XmlTextWriter(stringWriter);
            //message.WriteMessage(xmlTextWriter);
            //xmlTextWriter.Flush();
            //xmlTextWriter.Close();

            //String messageContent = stringWriter.ToString();
#endif
        }

        public object BeforeSendRequest(ref Message request,
            IClientChannel channel)
        {
            request = CredentialsHelper.AddCredentialsHeader(ref request);
            return null;
        }

        #region IDispatchMessageInspector Members

        #endregion
    }
}

第二- 添加代码以添加标题

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel;

namespace your_namespace
{

    public class CredentialsHelper
    {
       // siple string is for example - you can use your data structure here
        private static readonly string CredentialsHeaderName = "MyCredentials";
        private static readonly string CredentialsHeaderNamespace = "urn:Urn_probably_like_your_namespance";

        /// <summary>
        /// Update message with credentials
        /// </summary>
        public static Message AddCredentialsHeader(ref Message request)
        {

          string user = "John";
          string password = "Doe";

            string cred = string.Format("{0},{1}",   user, password);

            // Add header
            MessageHeader<string> header = new MessageHeader<string>(cred);
            MessageHeader untyped = header.GetUntypedHeader(CredentialsHeaderName, CredentialsHeaderNamespace);

            request = request.CreateBufferedCopy(int.MaxValue).CreateMessage();
            request.Headers.Add(untyped);

            return request;
        }

        /// <summary>
        /// Get details of current credentials from client-side added incoming headers
        /// 
        /// Return empty credentials when empty credentials specified 
        /// or when exception was occurred
        /// </summary>
        public static string GetCredentials()
        {
            string credentialDetails = string.Empty;
            try
            {
                credentialDetails = OperationContext.Current.IncomingMessageHeaders.
                    GetHeader<string>
                        (CredentialsHeaderName, CredentialsHeaderNamespace);
            }
            catch
            {
                    // TODO: ...
            }
            return credentialDetails;
        }

    }
}

第三- 在服务器端获取您的凭据

public void MyServerSideMethod()
{
   string credentials = CredentialsHelper.GetCredentials();
   . . . 
}

希望这可以帮助。

于 2013-04-14T05:12:56.890 回答
0

对于基本身份验证,我通过将安全模式设置为传输来使 WCF 正常工作。

例如在 web.config 中:

<system.serviceModel>
  <services>
    <service behaviorConfiguration="DefaultServiceBehavior" name="MyService">
      <endpoint address="basic" binding="basicHttpBinding" bindingConfiguration="BasicAuthenticationBinding" name="MyEndpoint" contract="MyContract" />
    </service>
  </services>
  <bindings>
    <basicHttpBinding>
      <binding name="BasicAuthenticationBinding">
        <security mode="Transport">
          <transport clientCredentialType="Basic" />
        </security>
      </binding>
    </basicHttpBinding>
   </bindings>
</system.serviceModel>
于 2016-10-28T11:24:37.933 回答