简体   繁体   English

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

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

My problem is the following. 我的问题如下。

A client interacts with my WCF service via a web interface (HTTP). 客户端通过Web界面(HTTP)与我的WCF服务进行交互。 Some service operations require the client to authenticate by providing username and password. 某些服务操作要求客户端通过提供用户名和密码进行身份验证。 Let's suppose for simplicity that these info are passed via query string parameters (or in the Authorization header as in HTTP Basic Auth). 为了简单起见,我们假设这些信息是通过查询字符串参数传递的(或在HTTP Auth中的Authorization标头中)传递的。

Eg, a service operation may be called via http://myhost.com/myservice/myop?user=xxx&password=yyy 例如,可以通过http://myhost.com/myservice/myop?user=xxx&password=yyy调用服务操作

As multiple service operations require such sort of authentication, I would like to factor the authentication code out of the single operations. 由于多个服务操作需要这种身份验证,因此我想将身份验证代码从单个操作中分解出来。

By looking around, I read about service behaviors and came up with the following code: 通过环顾四周,我了解了服务行为并提出了以下代码:

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;
    }

    /*...*/

}

Then, the service is annotated through 然后,通过以下方式对服务进行注释

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

Now, I manage to execute my behavior before ANY service operation. 现在,我设法在执行任何服务操作之前执行我的行为。 However, I have the following issues: 但是,我有以下问题:

  • How can I pass the result of authentication to the service operations ? 如何将身份验证结果传递给服务操作
  • How can I restrict the authentication to just a few service operations ? 如何将身份验证限制为仅进行一些服务操作

Regarding the last point, I looked at IOperationBehavior, but in that case I can just attach IParameterInspectors and not IDispatchMessageInspectors. 关于最后一点,我看了看IOperationBehavior,但是在那种情况下,我只能附加IParameterInspectors而不附加IDispatchMessageInspectors。 It would be undesirable because I may need to look at the message headers, for example in case I decide to consider the Authorization HTTP header when supporting HTTP Basic Authentication. 这是不希望的,因为我可能需要查看消息标头,例如,当我决定在支持HTTP基本身份验证时考虑考虑Authorization HTTP标头时。

As a related question, I would also ask what you think about my approach, and if there are better (non-overcomplicated) approaches. 作为一个相关的问题,我还想问一下您对我的方法的看法,以及是否有更好的(简单的)方法。

I would suggest isolating all of your methods that don't require authentication into their own service. 我建议将不需要身份验证的所有方法隔离到它们自己的服务中。 For example: 例如:

IPublicService.cs and PublicService.svc IPublicService.cs和PublicService.svc

and the ones that require authentication: 以及需要认证的:

IPrivateService.cs and PrivateService.svc IPrivateService.cs和PrivateService.svc

For authentication for PrivateService.svc, I'd suggest using MessageCredential using Username for that binding: 对于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>

and add a custom username validator class: 并添加一个自定义的用户名验证器类:

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;
        }
    }

And register your class in web.config: 并在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>

After some research, here is my current solution. 经过研究,这是我当前的解决方案。

Issue 1: execute (part of) the service behavior only for certain service operations 问题1:仅对某些服务操作执行(部分)服务行为

First of all, I mark my service operations with a custom attribute: 首先,我使用自定义属性标记我的服务操作:

public class RequiresAuthAttribute : Attribute { }

public partial class MyService { 

    [RequiresAuth]
    WebGet(UriTemplate = "...")]
    public Tresult MyServiceOperation(...){ ... }

Then I retrieve this information to decide if the behavior has to be executed or not 然后,我检索此信息来确定是否必须执行该行为

    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;
    }

Issue 2: pass information to the service operation 问题2:将信息传递给服务操作

The service behavior will do something as 服务行为将作为

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

while the service operation will do something as 而服务操作将

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

Alternatively, it is also possible to set a value for the service operation's parameters via 也可以通过以下方式为服务操作的参数设置一个值

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

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

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