【问题标题】:WCF: authenticate user before executing some service operationWCF:在执行某些服务操作之前对用户进行身份验证
【发布时间】:2014-10-21 13:42:11
【问题描述】:

我的问题如下。

客户端通过 Web 界面 (HTTP) 与我的 WCF 服务进行交互。 某些服务操作需要客户端通过提供用户名和密码进行身份验证。 为简单起见,我们假设这些信息是通过查询字符串参数(或在 HTTP 基本身份验证中的 Authorization 标头中)传递的。

例如,可以通过http://myhost.com/myservice/myop?user=xxx&password=yyy调用服务操作

由于多个服务操作需要这种类型的身份验证,我想将身份验证代码排除在单个操作之外。

通过环顾四周,我了解了服务行为并想出了以下代码:

public class MyAuthBehaviorAttribute : Attribute, IServiceBehavior, IDispatchMessageInspector {


    /********************/
    /* IServiceBehavior */
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
        System.ServiceModel.ServiceHostBase serviceHostBase) {
        //  It’s called right after the runtime was initialized
            foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers) {
                foreach (EndpointDispatcher epDisp in chDisp.Endpoints) {
                    epDisp.DispatchRuntime.MessageInspectors.Add(new MyAuthBehaviorAttribute());
                }
            }
    }

    /*...*/

    /*****************************/
    /* IDispatchMessageInspector */
    public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, 
        System.ServiceModel.IClientChannel channel, 
        System.ServiceModel.InstanceContext instanceContext) {
            object correlationState = null;
            var prop = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];


            var parts = HttpUtility.ParseQueryString(prop.QueryString);
            string user = parts["user"];
            string password = parts["password"];
            if (AuthenticateUser(user,password)) {
                // ???????????????????????????
            }
            else {
                throw new Exception("...");
            }

            return correlationState;
    }

    /*...*/

}

然后,通过注解服务

[MyAuthBehavior]
public class Service : IContract
{
    // implementation of the IContract interface
}

现在,我设法在任何服务操作之前执行我的行为。 但是,我有以下问题:

  • 如何将身份验证结果传递给服务操作
  • 如何将身份验证限制为仅几个服务操作

关于最后一点,我查看了 IOperationBehavior,但在这种情况下,我可以只附加 IParameterInspectors 而不是 IDispatchMessageInspectors。这是不可取的,因为我可能需要查看消息标头,例如,如果我决定在支持 HTTP 基本身份验证时考虑 Authorization HTTP 标头。

作为一个相关问题,我还想问一下您对我的方法的看法,以及是否有更好(不过分复杂)的方法。

【问题讨论】:

    标签: c# web-services wcf authentication


    【解决方案1】:

    经过一番研究,这是我目前的解决方案。

    问题1:仅对某些服务操作执行(部分)服务行为

    首先,我用自定义属性标记我的服务操作:

    public class RequiresAuthAttribute : Attribute { }
    
    public partial class MyService { 
    
        [RequiresAuth]
        WebGet(UriTemplate = "...")]
        public Tresult MyServiceOperation(...){ ... }
    

    然后我检索此信息以决定是否必须执行该行为

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, 
            System.ServiceModel.IClientChannel channel, 
            System.ServiceModel.InstanceContext instanceContext) {
            if(AuthenticationNeeded()){ ... }
        }
    
        public bool AuthenticationNeeded() {
            // 1) Get the current operation's description
            OperationDescription od = GetOperationDescription(OperationContext.Current);
    
            // 2) Check if the service operation is annotated with the [RequiresAuth] attribute
            Type contractType = od.DeclaringContract.ContractType;
            object[] attr = contractType.GetMethod(od.Name).GetCustomAttributes(typeof(RequiresAuthAttribute), false);
    
            if (attr == null || attr.Length == 0) return false;
            return true;
        }
    
        // See http://www.aspnet4you.com/wcf/index.php/2013/01/30/message-interception-auditing-and-logging-at-wcf-pipeline/ 
        private OperationDescription GetOperationDescription(OperationContext operationContext) {
            OperationDescription od = null;
            string bindingName = operationContext.EndpointDispatcher.ChannelDispatcher.BindingName;
            string methodName;
            if (bindingName.Contains("WebHttpBinding")) {
                //REST request
                methodName = (string)operationContext.IncomingMessageProperties["HttpOperationName"];
            }
            else {
                //SOAP request
                string action = operationContext.IncomingMessageHeaders.Action;
                methodName = operationContext.EndpointDispatcher.DispatchRuntime.Operations.FirstOrDefault(o => o.Action == action).Name;
            }
    
            EndpointAddress epa = operationContext.EndpointDispatcher.EndpointAddress;
            ServiceDescription hostDesc = operationContext.Host.Description;
            ServiceEndpoint ep = hostDesc.Endpoints.Find(epa.Uri);
    
            if (ep != null) {
                od = ep.Contract.Operations.Find(methodName);
            }
    
            return od;
        }
    

    问题2:向服务操作传递信息

    服务行为会做一些事情

    OperationContext.Current.IncomingMessageProperties.Add("myInfo", myInfo);
    

    虽然服务操作会做一些事情

    object myInfo = null;
    OperationContext.Current.IncomingMessageProperties.TryGetValue("myInfo", out myInfo);
    

    或者,也可以通过设置服务操作的参数值

    WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BoundVariables["MYPARAM"] = myParam;
    

    【讨论】:

      【解决方案2】:

      我建议将不需要身份验证的所有方法隔离到自己的服务中。例如:

      IPublicService.cs 和 PublicService.svc

      以及需要身份验证的:

      IPrivateService.cs 和 PrivateService.svc

      对于 PrivateService.svc 的身份验证,我建议使用 MessageCredential using Username 进行该绑定:

        <wsHttpBinding>
          <binding name="wsHttpEndpointBinding" closeTimeout="00:30:00" openTimeout="00:30:00" receiveTimeout="00:30:00" sendTimeout="00:30:00" maxReceivedMessageSize="500000000">
            <readerQuotas maxDepth="500000000" maxStringContentLength="500000000" maxArrayLength="500000000" maxBytesPerRead="500000000" maxNameTableCharCount="500000000" />
            <security mode="MessageCredential">
              <message clientCredentialType="UserName" />
            </security>
          </binding>
        </wsHttpBinding>
      

      并添加自定义用户名验证器类:

      public class CustomUserNameValidator : UserNamePasswordValidator
          {
              public override void Validate(string userName, string password)
              {
                  if (username!="test" && password!="test")
                  {
                  throw new FaultException("Unknown username or incorrect password.");
                  }
                  return;
              }
          }
      

      并在 web.config 中注册您的课程:

      <behaviors>
        <serviceBehaviors>
          <behavior>
            <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
            <serviceMetadata httpsGetEnabled="true" />
            <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
            <serviceDebug includeExceptionDetailInFaults="true" />
            <serviceCredentials>
              <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyProgram.CustomUserNameValidator,MyProgram" />
            </serviceCredentials>
          </behavior>
        </serviceBehaviors>
      </behaviors>
      

      【讨论】:

        猜你喜欢
        • 2011-08-29
        • 2012-07-28
        • 2011-10-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多