使用C#连接Workday WSDL时持续报无效用户名/密码错误的排查求助
嘿,我来帮你排查下这个棘手的问题!当对方已经确认账号配置没问题,但你还是收到无效用户名/密码的SOAP错误时,通常是配置细节或者Workday特定要求没匹配上,结合你的代码和配置,给你几个排查方向:
1. 检查绑定配置的安全模式与Workday要求匹配
你在代码里手动创建了CustomBinding,用的是CreateUserNameOverTransportBindingElement(),这对应Transport安全模式,但有些Workday实例要求的是TransportWithMessageCredential模式。另外,你设置了sb.IncludeTimestamp = false,而Workday的大多数API要求包含时间戳来防止重放攻击,试着把这个改成true试试:
sb.IncludeTimestamp = true;
2. 确认用户名格式与租户正确性
Workday的用户名格式通常有几种:租户ID\用户名、用户名@租户ID或者纯用户名(租户在Endpoint中指定)。你当前用的是user@tenant,可以和对方确认下正确的格式,同时检查Endpoint地址里的tenant部分是否是准确的租户ID,有没有拼写错误。
3. 排查证书信任导致的隐藏错误
有时候TLS握手时的证书信任问题,会被SOAP客户端包装成无效凭据错误。你可以临时添加证书验证回调来排查(注意生产环境不要用这个):
// 在Main方法开头添加 ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
如果加上之后能成功,说明是本地证书存储缺少Workday的根证书,需要导入对应的证书。
4. 验证用户的API权限
Workday的错误提示有时候不够准确——即使账号存在,如果没有Get Purchase Orders这个API的访问权限,也可能返回无效用户名/密码的错误。可以让对方确认该用户是否被分配了对应的API权限组。
5. 尝试使用配置文件的绑定而非自定义Binding
你代码里的自定义Binding可能存在细节问题,试着直接用app.config里的配置来创建客户端,排除自定义Binding的影响:
// 替换CreateWorkdayClient方法的实现 public static Resource_ManagementPortClient CreateWorkdayClient() { var client = new Resource_ManagementPortClient("Resource_Management"); client.ClientCredentials.UserName.UserName = "user@tenant"; client.ClientCredentials.UserName.Password = "password"; // 同样设置TLS1.2 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; return client; }
6. 检查API版本兼容性
你用的是version = "v42.0",确认下这个版本和目标Workday实例的版本是否兼容,有些旧版本的Workday可能不支持较新的API版本,或者反之。
另外,你也可以启用WCF的消息日志,查看实际发送的SOAP请求内容,确认用户名密码是否正确传递到了Workday端:
在app.config里添加日志配置:
<system.diagnostics> <sources> <source name="System.ServiceModel.MessageLogging"> <listeners> <add name="messages" type="System.Diagnostics.XmlWriterTraceListener" initializeData="messages.svclog" /> </listeners> </source> </sources> <trace autoflush="true" /> </system.diagnostics> <system.serviceModel> <!-- 原有配置 --> <diagnostics> <messageLogging logEntireMessage="true" logMalformedMessages="true" logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" maxMessagesToLog="3000" /> </diagnostics> </system.serviceModel>
运行程序后查看生成的messages.svclog文件,就能看到实际发送的请求细节,确认凭据是否正确。
你的原始代码与配置(供参考)
Program.cs
internal class Program { static void Main( string[] args ) { ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; using( var clt = CreateWorkdayClient() ) { var request = new Get_Purchase_Orders_RequestType() { version = "v42.0", Item = new Purchase_Order_Request_CriteriaType() { }, Response_Filter = new Response_FilterType() { Page = 1, PageSpecified = true, Count = 50, CountSpecified = true }, Response_Group = new Purchase_Order_Response_GroupType() { Include_Reference = true, Include_ReferenceSpecified = true }, }; var response = clt.Get_Purchase_Orders( request ); Debug.WriteLine( JsonConvert.SerializeObject( response, Formatting.Indented, new JsonConverter[] { new StringEnumConverter() } ) ); } } public static Resource_ManagementPortClient CreateWorkdayClient() { SecurityBindingElement sb = SecurityBindingElement.CreateUserNameOverTransportBindingElement(); sb.IncludeTimestamp = false; const int lim = Int32.MaxValue; var timeout = TimeSpan.FromMinutes( 2 ); var cb = new CustomBinding( sb, new TextMessageEncodingBindingElement( MessageVersion.Soap11, Encoding.UTF8 ) { ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas { MaxDepth = lim, MaxStringContentLength = lim, MaxArrayLength = lim, MaxBytesPerRead = lim, MaxNameTableCharCount = lim } }, new HttpsTransportBindingElement { MaxBufferPoolSize = lim, MaxReceivedMessageSize = lim, MaxBufferSize = lim, Realm = string.Empty } ) { SendTimeout = timeout, ReceiveTimeout = timeout }; var client = new Resource_ManagementPortClient( cb, new EndpointAddress( "https://wd2-impl-services1.workday.com/ccx/service/tenant/Resource_Management" ) ); client.ClientCredentials.UserName.UserName = "user@tenant"; client.ClientCredentials.UserName.Password = "password"; return client; } }
app.config
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="Resource_ManagementBinding"> <security mode="Transport" /> </binding> <binding name="Resource_ManagementBinding1" /> </basicHttpBinding> </bindings> <client> <endpoint address="https://wd2-impl-services1.workday.com/ccx/service/tenant/Resource_Management" binding="basicHttpBinding" bindingConfiguration="Resource_ManagementBinding" contract="TestApp.Resource_ManagementPort" name="Resource_Management" /> </client> </system.serviceModel>
备注:内容来源于stack exchange,提问作者Matt James

