使用WCF SOAP API中的HTTP授权标头进行身份validation

我们目前有一个WCF SOAP API,允许使用者使用用户名和密码进行身份validation(内部使用UserNamePasswordValidator )作为参考,用户名和密码在SOAP Body中传递,如下所示:

   2013-04-05T16:35:07.341Z 2013-04-05T16:40:07.341Z   someusername somepassword    

我们还希望另外支持使用者在HTTP Authorization标头中指定凭据,如基本身份validation或OAuth承载令牌

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

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

您可以采用的方法之一是使用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 { ///  /// /************************************ /// * /// * 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 /// * /// *********************/ ///  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"; ///  /// Update message with credentials ///  public static Message AddCredentialsHeader(ref Message request) { string user = "John"; string password = "Doe"; string cred = string.Format("{0},{1}", user, password); // Add header MessageHeader header = new MessageHeader(cred); MessageHeader untyped = header.GetUntypedHeader(CredentialsHeaderName, CredentialsHeaderNamespace); request = request.CreateBufferedCopy(int.MaxValue).CreateMessage(); request.Headers.Add(untyped); return request; } ///  /// Get details of current credentials from client-side added incoming headers /// /// Return empty credentials when empty credentials specified /// or when exception was occurred ///  public static string GetCredentials() { string credentialDetails = string.Empty; try { credentialDetails = OperationContext.Current.IncomingMessageHeaders. GetHeader (CredentialsHeaderName, CredentialsHeaderNamespace); } catch { // TODO: ... } return credentialDetails; } } } 

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

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

希望这可以帮助。

您应该考虑为WCF服务实现ServiceAuthorizationManager以处理HTTP Authorization标头授权。

创建一个inheritance自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(); if (saz == null) { saz = new ServiceAuthorizationBehavior(); restAPIServiceHost.Description.Behaviors.Add(saz); } saz.ServiceAuthorizationManager = new MyServiceAuthorizationManager(); restAPIServiceHost.Open(); 

这将为WCF服务公开的每个方法注入一个授权检查,而不需要对服务方法本身进行任何更改。

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

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

当您必须接受多个身份validation表示时,您还需要确定优先级。 如果存在HTTP Authorization标头,我怀疑它应该优先于SOAP消息中包含的任何内容。 如果HTTP Authorization标头存在但无效,则完全停止 – 拒绝未经授权的请求。 SOAP内容无关紧要 – 无效的HTTP Authorization标头总是坏消息。 如果根本没有HTTP Authorization标头,那么您可以查看是否存在SOAP安全元素,您可以从中获取SOAP用户凭据并测试它们的有效性。

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

例如在web.config中: