WCF REST在某些方法上的基本身份验证

6

我在 WCF 4.0 中实现了相当多的 RESTful(GET 和 POST) 方法。所有这些方法都是通过 SSL 安全传输的。

以下是其中一些方法的示例:

[OperationContract]
[WebInvoke(UriTemplate = "Login?", Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
LoginResponse Login(LoginRequest request);

[OperationContract]
[WebInvoke(UriTemplate = "UpdateDetails?", Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
UpdateUserDetailResponse UpdateDetails(UpdateUserDetailRequest request);

[OperationContract]
[WebInvoke(UriTemplate = "GetDetails?", Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
UserDetailResponse GetDetails(UserDetailRequest request);

我查看了很多博客和论坛,仍然找不到符合我的要求的内容。我需要在某些方法上实现基本身份验证,但不是全部。如果您查看上面的示例,我需要通过UpdateDetails和GetDetails方法发送用户名和密码,但不需要Login方法。然后将根据数据库对用户名和密码进行身份验证。这样做是否可行?
顺便说一句:许多不同的移动设备调用这些REST方法。
我查看了以下网站,它们都在REST上实现了基本认证,但覆盖了上述所有方法。
- http://msdn.microsoft.com/en-us/library/aa702565.aspx - 向WCF REST服务添加基本HTTP身份验证 - http://custombasicauth.codeplex.com/(底部的链接不再有效)

我想做的事情是否可能实现?

1个回答

1
我创建了一个BasicAuthenticationInvoker类,你可以在需要进行身份验证的方法上使用它,如下所示:
  [OperationContract]
    [BasicAuthenticationInvoker] // this is the auth attribute!
    [WebInvoke(UriTemplate = "QuickQuote?", Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
    QuickQuoteResponse QuickQuote(QuickQuoteRequest request);

实际的类如下所示:

 public class BasicAuthenticationInvoker : Attribute, IOperationBehavior, IOperationInvoker
    {
        #region Private Fields

        private IOperationInvoker _invoker;

        #endregion

        #region IOperationBehavior Members

        public void ApplyDispatchBehavior(OperationDescription operationDescription,
                                          DispatchOperation dispatchOperation)
        {
            _invoker = dispatchOperation.Invoker;
            dispatchOperation.Invoker = this;
        }

        public void ApplyClientBehavior(OperationDescription operationDescription,
                                        ClientOperation clientOperation)
        {
        }

        public void AddBindingParameters(OperationDescription operationDescription,
                                         BindingParameterCollection bindingParameters)
        {
        }

        public void Validate(OperationDescription operationDescription)
        {
        }

        #endregion

        #region IOperationInvoker Members

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            if (Authenticate("Client Name here"))
                return _invoker.Invoke(instance, inputs, out outputs);
            else
            {
                outputs = null;
                return null;
            }
        }

        public object[] AllocateInputs()
        {
            return _invoker.AllocateInputs();
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs,
                                        AsyncCallback callback, object state)
        {
            throw new NotSupportedException();
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            throw new NotSupportedException();
        }

        public bool IsSynchronous
        {
            get
            {
                return true;
            }
        }

        #endregion

        private bool Authenticate(string realm)
        {
            string[] credentials = GetCredentials(WebOperationContext.Current.IncomingRequest.Headers);

            if (credentials != null && credentials.Length == 2)
            {
                // do auth here
                var username = credentials[0];
                var password = credentials[1];

               // validate the username and password against whatever auth logic you have

                return true; // if successful
            }

            WebOperationContext.Current.OutgoingResponse.Headers["WWW-Authenticate"] = string.Format("Basic realm=\"{0}\"", realm);
            WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
            return false;
        }

        private string[] GetCredentials(WebHeaderCollection headers)
        {
            string credentials = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
            if (credentials != null)
                credentials = credentials.Trim();

            if (!string.IsNullOrEmpty(credentials))
            {
                try
                {
                    string[] credentialParts = credentials.Split(new[] {' '});
                    if (credentialParts.Length == 2 && credentialParts[0].Equals("basic", StringComparison.OrdinalIgnoreCase))
                    {
                        credentials = Encoding.ASCII.GetString(Convert.FromBase64String(credentialParts[1]));
                        credentialParts = credentials.Split(new[] {':'});
                        if (credentialParts.Length == 2)
                            return credentialParts;
                    }
                }
                catch
                {
                }
            }

            return null;
        }
    }

如果我想让QuickQuote的响应取决于登录的用户怎么办?换句话说,我如何在调用QuickQuote方法时获取凭据? - Stijn Tallon
你应该真正考虑像ServiceStack这样的东西,而不是WCF。 - Dylan
我知道这是老问题了,但是@Dylan,Authenticate("Client name here")中应该填什么? - andrewb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接