WCF:在执行某些服务操作之前验证用户

时间:2014-10-21 13:42:12

标签: c# web-services wcf authentication

我的问题如下。

客户端通过Web界面(HTTP)与我的WCF服务进行交互。 某些服务操作要求客户端通过提供用户名和密码进行身份验证。 让我们假设这些信息是通过查询字符串参数传递的(或者在HTTP Basic Auth中的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基本身份验证时考虑授权HTTP头。

作为一个相关的问题,我也会问你对我的方法的看法,以及是否有更好的(非过于复杂的)方法。

2 个答案:

答案 0 :(得分:1)

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

IPublicService.cs和PublicService.svc

以及需要身份验证的内容:

IPrivateService.cs和PrivateService.svc

对于PrivateService.svc的身份验证,我建议使用MessageCredential使用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>

答案 1 :(得分: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;