[英]How can I pass a username/password in the header to a SOAP WCF Service
我正在尝试使用第三方 Web 服务https://staging.identitymanagement.lexisnexis.com/identity-proofing/services/identityProofingServiceWS/v2?wsdl
我已经将它添加为服务引用,但我不确定如何传递标头的凭据。
如何使标头请求匹配此格式?
<soapenv:Header>
<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-49" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>12345/userID</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/ oasis-200401-wss-username-token-profile-1.0#PasswordText">password123</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">d+VxCZX1cH/ieMkKEr/ofA==</wsse:Nonce>
<wsu:Created>2012-08-04T20:25:04.038Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
楼上的答案都错了! 不要添加自定义标题。 从您的示例 xml 来看,它是一个标准的 WS-Security 标头。 WCF 绝对支持它开箱即用。 添加服务引用时,您应该在配置文件中为您创建 basicHttpBinding 绑定。 您必须修改它以包含具有模式 TransportWithMessageCredential 的安全元素和具有 clientCredentialType = UserName 的消息元素:
<basicHttpBinding>
<binding name="usernameHttps">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</basicHttpBinding>
上面的配置告诉 WCF 在 HTTPS 上的 SOAP 标头中期望用户 ID/密码。 然后,您可以在拨打电话之前在代码中设置 ID/密码:
var service = new MyServiceClient();
service.ClientCredentials.UserName.UserName = "username";
service.ClientCredentials.UserName.Password = "password";
除非这个特定的服务提供商偏离标准,否则它应该可以工作。
可能有更聪明的方法,但您可以像这样手动添加标题:
var client = new IdentityProofingService.IdentityProofingWSClient();
using (new OperationContextScope(client.InnerChannel))
{
OperationContext.Current.OutgoingMessageHeaders.Add(
new SecurityHeader("UsernameToken-49", "12345/userID", "password123"));
client.invokeIdentityService(new IdentityProofingRequest());
}
这里, SecurityHeader
是一个自定义实现的类,它需要一些其他类,因为我选择使用属性来配置 XML 序列化:
public class SecurityHeader : MessageHeader
{
private readonly UsernameToken _usernameToken;
public SecurityHeader(string id, string username, string password)
{
_usernameToken = new UsernameToken(id, username, password);
}
public override string Name
{
get { return "Security"; }
}
public override string Namespace
{
get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
XmlSerializer serializer = new XmlSerializer(typeof(UsernameToken));
serializer.Serialize(writer, _usernameToken);
}
}
[XmlRoot(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
public class UsernameToken
{
public UsernameToken()
{
}
public UsernameToken(string id, string username, string password)
{
Id = id;
Username = username;
Password = new Password() {Value = password};
}
[XmlAttribute(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")]
public string Id { get; set; }
[XmlElement]
public string Username { get; set; }
[XmlElement]
public Password Password { get; set; }
}
public class Password
{
public Password()
{
Type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";
}
[XmlAttribute]
public string Type { get; set; }
[XmlText]
public string Value { get; set; }
}
我没有在UsernameToken
XML 中添加Nonce
位,但它与Password
非常相似。 还需要添加Created
元素,但它是一个简单的[XmlElement]
。
添加自定义的硬编码标头可能会起作用(有时也可能会被拒绝),但这完全是错误的做法。 WSSE 的目的是安全。 正是出于这个原因,Microsoft 发布了 Microsoft Web Services Enhancements 2.0,随后发布了 WSE 3.0。 您需要安装此软件包 ( http://www.microsoft.com/en-us/download/details.aspx?id=14089 )。
该文档不容易理解,特别是对于那些没有使用过 SOAP 和 WS-Addressing 的人。 首先,“BasicHttpBinding”是 Soap 1.1,它不会为您提供与 WSHttpBinding 相同的消息头。 安装包并查看示例。 您将需要从 WSE 3.0 引用 DLL,并且您还需要正确设置您的消息。 WS Addressing 标头有大量的变化。 您正在寻找的是 UsernameToken 配置。
这是一个更长的解释,我应该为每个人写点东西,因为我在任何地方都找不到正确的答案。 您至少需要从 WSE 3.0 包开始。
表明 WCF 开箱即用支持问题中提供的标题的答案不正确。 问题中的标头在 UsernameToken 中包含Nonce和Created时间戳,这是 WCF 不支持的 WS-Security 规范的官方部分。 WCF 仅支持开箱即用的用户名和密码。
如果您需要做的只是添加用户名和密码,那么 Sergey 的答案是最省力的方法。 如果需要添加任何其他字段,则需要提供自定义类来支持它们。
我发现一种更优雅的方法是覆盖 ClientCredentials、ClientCredentialsSecurityTokenManager 和 WSSecurityTokenizer 类以支持其他属性。 我已经提供了详细讨论该方法的博客文章的链接,但这里是覆盖的示例代码:
public class CustomCredentials : ClientCredentials
{
public CustomCredentials()
{ }
protected CustomCredentials(CustomCredentials cc)
: base(cc)
{ }
public override System.IdentityModel.Selectors.SecurityTokenManager CreateSecurityTokenManager()
{
return new CustomSecurityTokenManager(this);
}
protected override ClientCredentials CloneCore()
{
return new CustomCredentials(this);
}
}
public class CustomSecurityTokenManager : ClientCredentialsSecurityTokenManager
{
public CustomSecurityTokenManager(CustomCredentials cred)
: base(cred)
{ }
public override System.IdentityModel.Selectors.SecurityTokenSerializer CreateSecurityTokenSerializer(System.IdentityModel.Selectors.SecurityTokenVersion version)
{
return new CustomTokenSerializer(System.ServiceModel.Security.SecurityVersion.WSSecurity11);
}
}
public class CustomTokenSerializer : WSSecurityTokenSerializer
{
public CustomTokenSerializer(SecurityVersion sv)
: base(sv)
{ }
protected override void WriteTokenCore(System.Xml.XmlWriter writer,
System.IdentityModel.Tokens.SecurityToken token)
{
UserNameSecurityToken userToken = token as UserNameSecurityToken;
string tokennamespace = "o";
DateTime created = DateTime.Now;
string createdStr = created.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
// unique Nonce value - encode with SHA-1 for 'randomness'
// in theory the nonce could just be the GUID by itself
string phrase = Guid.NewGuid().ToString();
var nonce = GetSHA1String(phrase);
// in this case password is plain text
// for digest mode password needs to be encoded as:
// PasswordAsDigest = Base64(SHA-1(Nonce + Created + Password))
// and profile needs to change to
//string password = GetSHA1String(nonce + createdStr + userToken.Password);
string password = userToken.Password;
writer.WriteRaw(string.Format(
"<{0}:UsernameToken u:Id=\"" + token.Id +
"\" xmlns:u=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
"<{0}:Username>" + userToken.UserName + "</{0}:Username>" +
"<{0}:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">" +
password + "</{0}:Password>" +
"<{0}:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" +
nonce + "</{0}:Nonce>" +
"<u:Created>" + createdStr + "</u:Created></{0}:UsernameToken>", tokennamespace));
}
protected string GetSHA1String(string phrase)
{
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
return Convert.ToBase64String(hashedDataBytes);
}
}
在创建客户端之前,您需要创建自定义绑定并向其手动添加安全性、编码和传输元素。 然后,用您的自定义实现替换默认的 ClientCredentials 并像往常一样设置用户名和密码:
var security = TransportSecurityBindingElement.CreateUserNameOverTransportBindingElement();
security.IncludeTimestamp = false;
security.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256;
security.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
var encoding = new TextMessageEncodingBindingElement();
encoding.MessageVersion = MessageVersion.Soap11;
var transport = new HttpsTransportBindingElement();
transport.MaxReceivedMessageSize = 20000000; // 20 megs
binding.Elements.Add(security);
binding.Elements.Add(encoding);
binding.Elements.Add(transport);
RealTimeOnlineClient client = new RealTimeOnlineClient(binding,
new EndpointAddress(url));
client.ChannelFactory.Endpoint.EndpointBehaviors.Remove(client.ClientCredentials);
client.ChannelFactory.Endpoint.EndpointBehaviors.Add(new CustomCredentials());
client.ClientCredentials.UserName.UserName = username;
client.ClientCredentials.UserName.Password = password;
显然,这篇文章已经存在好几年了 - 但事实是我在寻找类似问题时确实找到了它。 在我们的例子中,我们必须将用户名/密码信息添加到 Security 标头中。 这与在 Security 标头之外添加标头信息不同。
执行此操作的正确方法(对于自定义绑定/authenticationMode="CertificateOverTransport")(如 .Net 框架版本 4.6.1),是像往常一样添加客户端凭据:
client.ClientCredentials.UserName.UserName = "[username]";
client.ClientCredentials.UserName.Password = "[password]";
然后在安全绑定元素中添加一个“令牌”——因为当身份验证模式设置为证书时,默认情况下不会包含用户名/密码凭据。
你可以像这样设置这个令牌:
//Get the current binding
System.ServiceModel.Channels.Binding binding = client.Endpoint.Binding;
//Get the binding elements
BindingElementCollection elements = binding.CreateBindingElements();
//Locate the Security binding element
SecurityBindingElement security = elements.Find<SecurityBindingElement>();
//This should not be null - as we are using Certificate authentication anyway
if (security != null)
{
UserNameSecurityTokenParameters uTokenParams = new UserNameSecurityTokenParameters();
uTokenParams.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
security.EndpointSupportingTokenParameters.SignedEncrypted.Add(uTokenParams);
}
client.Endpoint.Binding = new CustomBinding(elements.ToArray());
那应该这样做。 如果没有上述代码(显式添加用户名令牌),即使在客户端凭据中设置用户名信息也可能不会导致这些凭据传递给服务。
假设您的web.config
中有名称为localhost
服务引用,因此您可以执行以下操作
localhost.Service objWebService = newlocalhost.Service();
localhost.AuthSoapHd objAuthSoapHeader = newlocalhost.AuthSoapHd();
string strUsrName =ConfigurationManager.AppSettings["UserName"];
string strPassword =ConfigurationManager.AppSettings["Password"];
objAuthSoapHeader.strUserName = strUsrName;
objAuthSoapHeader.strPassword = strPassword;
objWebService.AuthSoapHdValue =objAuthSoapHeader;
string str = objWebService.HelloWorld();
Response.Write(str);
假设您正在使用 HttpWebRequest 和 HttpWebResponse 调用 Web 服务,因为 .Net 客户端不支持您尝试使用的 WSLD 结构。
在这种情况下,您可以在标头上添加安全凭据,例如:
<soap:Envelpe>
<soap:Header>
<wsse:Security soap:mustUnderstand='true' xmlns:wsse='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'><wsse:UsernameToken wsu:Id='UsernameToken-3DAJDJSKJDHFJASDKJFKJ234JL2K3H2K3J42'><wsse:Username>YOU_USERNAME/wsse:Username><wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'>YOU_PASSWORD</wsse:Password><wsse:Nonce EncodingType='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary'>3WSOKcKKm0jdi3943ts1AQ==</wsse:Nonce><wsu:Created>2015-01-12T16:46:58.386Z</wsu:Created></wsse:UsernameToken></wsse:Security>
</soapHeather>
<soap:Body>
</soap:Body>
</soap:Envelope>
您可以使用 SOAPUI 通过 http 日志获取 wsse 安全性。
请小心,因为这不是一个安全的场景。
我从这里得到了一个更好的方法: WCF:创建自定义标题,如何添加和使用这些标题
客户识别自己
这里的目标是让客户端提供某种信息,服务器可以使用这些信息来确定谁在发送消息。 以下 C# 代码将添加一个名为 ClientId 的标头:
var cl = new ActiveDirectoryClient();
var eab = new EndpointAddressBuilder(cl.Endpoint.Address);
eab.Headers.Add( AddressHeader.CreateAddressHeader("ClientId", // Header Name
string.Empty, // Namespace
"OmegaClient")); // Header Value
cl.Endpoint.Address = eab.ToEndpointAddress();
// Now do an operation provided by the service.
cl.ProcessInfo("ABC");
该代码所做的是添加一个名为 ClientId 的端点标头,其值为 OmegaClient 以插入到没有命名空间的soap标头中。
客户端配置文件中的自定义标题
有另一种方式来做一个自定义标题。 这可以在客户端的 Xml 配置文件中实现,其中通过将自定义标头指定为端点的一部分来发送所有消息,如下所示:
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IActiveDirectory" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:41863/ActiveDirectoryService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IActiveDirectory"
contract="ADService.IActiveDirectory" name="BasicHttpBinding_IActiveDirectory">
<headers>
<ClientId>Console_Client</ClientId>
</headers>
</endpoint>
</client>
</system.serviceModel>
</configuration>
我在 web.config 中添加了 customBinding。
<configuration>
<system.serviceModel>
<bindings>
<customBinding>
<binding name="CustomSoapBinding">
<security includeTimestamp="false"
authenticationMode="UserNameOverTransport"
defaultAlgorithmSuite="Basic256"
requireDerivedKeys="false"
messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
</security>
<textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
<httpsTransport maxReceivedMessageSize="2000000000"/>
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="https://test.com:443/services/testService"
binding="customBinding"
bindingConfiguration="CustomSoapBinding"
contract="testService.test"
name="test" />
</client>
</system.serviceModel>
<startup>
<supportedRuntime version="v4.0"
sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
添加 customBinding 后,我可以将用户名和密码传递给客户端服务,如下所示:
service.ClientCridentials.UserName.UserName = "testUser";
service.ClientCridentials.UserName.Password = "testPass";
通过这种方式,您可以将标头中的用户名、密码传递给 SOAP WCF 服务。
如果这与 Peoplesoft 问题有关: https : //support.oracle.com/knowledge/PeopleSoft%20Enterprise/2370907_1.html
我需要在 Soap 密码上设置属性,而以前没有在该标签上设置该属性。
我只是在我的自定义绑定上设置 MessageSecurityVersion:
CustomBinding customBinding = new CustomBinding();
customBinding.Name = endpointName;
customBinding.CloseTimeout = TimeSpan.FromMinutes(1);
customBinding.OpenTimeout = TimeSpan.FromMinutes(1);
customBinding.SendTimeout = TimeSpan.FromMinutes(20);
customBinding.ReceiveTimeout = TimeSpan.FromMinutes(20);
TextMessageEncodingBindingElement textMessageElement = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
customBinding.Elements.Add(textMessageElement);
TransportSecurityBindingElement securityElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
securityElement.IncludeTimestamp = false;
securityElement.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
customBinding.Elements.Add(securityElement);
// ORDER MATTERS: THIS HAS TO BE LAST!!! - HVT
HttpsTransportBindingElement transportElement = new HttpsTransportBindingElement();
transportElement.MaxBufferSize = int.MaxValue;
transportElement.MaxReceivedMessageSize = int.MaxValue;
customBinding.Elements.Add(transportElement);
return customBinding;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.