使用WCF的Http请求中缺少授权头(Authorization Header)

9
我将使用WCF访问一个网络服务。我正在使用WSHttpBinding,安全模式设置为传输(https),客户端凭据类型为基本身份验证。当我使用代理访问服务时,会出现401未授权的异常。
以下是绑定信息:
var binding = new WSHttpBinding()
        {
            UseDefaultWebProxy = true,
            Security =
            {
                Mode = SecurityMode.Transport,
                Transport =
                {
                    ClientCredentialType = HttpClientCredentialType.Basic,
                },
            }
        };

这里是服务调用

var client = new InternetClient(binding, new EndpointAddress("httpsurl"));

        client.ClientCredentials.UserName.UserName = "username";
        client.ClientCredentials.UserName.Password = "password";
        client.ProcessMessage("somevalue");

当使用Http Analyzer查看Http头时
CONNECT HEADER
(请求行):CONNECT somehost.com:443 HTTP/1.1 主机:somehost.com 代理连接:保持活动状态
POST HEADER
(请求行):POST /Company/1.0 HTTP/1.1 内容类型:application/soap+xml; charset=utf-8 VsDebuggerCausalityData:uIDPo+voStemjalOv5LtRotFQ7UAAAAAUKLJpa755k6oRwto14BnuE2PDtYKxr9LhfqXFSOo8pEACQAA 主机:somehost.com 内容长度:898 期望:100-continue 连接:保持活动状态
如果您发现缺少授权标头
现在我的问题是为什么WCF调用缺少授权标头?我是否遗漏了什么?请告知需要更多信息。
5个回答

16

这是一个常见的问题,但情况与您想象的不同。

事实证明,对于第一次请求,配置为使用HTTP基本身份验证的WCF客户端将向服务器发送没有必要的授权头的请求。这是WCF客户端使用的HttpWebRequest类的默认行为。

通常,Web服务服务器将返回HTTP 401未经授权的响应给WCF客户端,然后客户端会重新发送带有授权头的消息。正常情况下,这意味着对于HTTP基本身份验证,将会有一个相当无用的往返到服务器。

这也解释了为什么在你嗅探到的消息中缺少头部信息。一些 HTTP 嗅探可能不会传递 401 响应,因此整个交换过程被搞砸了。

可以通过手动注入所需的授权头来避免服务器往返和依赖于 401 响应。请参阅例如如何手动将 Authorization header 注入 WCF 请求


什么?你确定这种行为是正确的吗?对我来说,它似乎相当荒谬。 - Johnny_D
是的,绝对确定 - 我进行了广泛的测试并使用 Fiddler 研究了其行为 - 推荐的解决方案按照描述的方式工作。我已经在生产环境中运行了它。 - whale70
是的,这对我也起作用了。但我仍然感到困惑。WCF开发人员创建身份验证机制是为了什么,如果它们不能直接使用并且我们必须自定义其行为?! - Johnny_D
救星,我实现了一个服务,它有这个问题,但我从来没有得到过往返调用。它只会调用一次而不添加头部。进行头部注入解决了这个问题。 - SomeRandomName
这解决了我遇到的问题,即在小负载中授权标头存在,但在较大(700K+)负载上,授权标头将被删除,并添加了以前不存在的保持活动标头。 - mlhDev

11

作为对之前问题的轻微修改,为了支持async/await调用,您实际上可以创建一个新的OperationContext并在任何您喜欢的线程上传递它(只要它不在并发线程之间共享,因为它不是线程安全的对象)

var client = new MyClient();
client.ClientCredentials.UserName.UserName = "username"; 
client.ClientCredentials.UserName.Password = "password";
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(client.ClientCredentials.UserName.UserName + ":" + client.ClientCredentials.UserName.Password));

var context = new OperationContext(ormClient.InnerChannel);
using (new OperationContextScope(context))
{
    context.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
    return await client.SomeMethod();
}

2

我也遇到了完全相同的问题。我使用以下代码手动注入授权头,解决了这个问题:

var callcontext = new CAdxCallContext();
callcontext.codeLang = "ENG";
callcontext.poolAlias = "BGRTEST";

var proxy = new CAdxWebServiceXmlCCClient();
proxy.Endpoint.EndpointBehaviors.Add(new CustomEndpoint()); 
proxy.ClientCredentials.UserName.UserName = "USERNAME"; // Might not benecessary
proxy.ClientCredentials.UserName.Password = "PASSWORD"; // Might not benecessary

string inputXml = "<PARAM>" +
       "<GRP ID= \"GRP1\">" +
       "<FLD NAME = \"ITMREF\">" + "100001" + "</FLD>" +
       "</GRP>" +
       "</PARAM>";

CAdxResultXml response;
try
{
    response = proxy.run(callcontext, "BGR_SIEPRO", inputXml);
}
catch (TimeoutException timeout)
{
    Console.WriteLine(timeout.Message);
    // handle the timeout exception.
    proxy.Abort();
}
catch (CommunicationException commexception)
{
    Console.WriteLine(commexception.Message);
    // handle the communication exception.
    proxy.Abort();
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}
finally
{
     proxy.Close();
}
}

    public class ClientMessageInspector : IClientMessageInspector
    {
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            // Nothing Here
            Console.Write(reply.ToString());
        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
            httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " +
                Convert.ToBase64String(Encoding.ASCII.GetBytes("USERNAME" + ":" +
                    "PASSWORD"));
            request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestProperty);
            return null;
        }
    }

    public class CustomEndpoint : IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
            // Nothing here
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.ClientMessageInspectors.Add(new ClientMessageInspector());
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            // Nothing here
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            // Nothing here
        }
    }

1
注意头部的Expect:100-continue。这就是往返旅行的原因。
将此放入您的web.config并重试:
<system.net>
    <settings>
      <servicePointManager expect100Continue="false"/>
    </settings>
</system.net>

这将删除 Expect: 100 Continue 头,但不会添加 Authorization 头。 - hectorct

0

实际上,我对这个问题的看法是错误的。当运行HTTP分析器时,我确实看到了不同的行为。在收到401响应后,我的应用程序崩溃了。当Http分析器应用程序关闭时,上述代码按预期工作。


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