繁体   English   中英

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

[英]WCF: authenticate user before executing some service operation

我的问题如下。

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

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

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

IPublicService.cs和PublicService.svc

以及需要认证的:

IPrivateService.cs和PrivateService.svc

对于PrivateService.svc的身份验证,我建议使用MessageCredential并将该用户名用于该绑定:

  <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:仅对某些服务操作执行(部分)服务行为

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

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;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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