Мне нужно добавить ссылку на службу, чтобы создать клиентский прокси для службы .Net Framework и службы .Net 8. Справочник по добавлению службы .Net 8 правильно генерирует клиентский прокси и выполняется без проблем. В отдельном проекте или службе на основе Framework созданный прокси-сервер не включает ClientCredentials.
Для следующей политики из wsdl:
<wsp:Policy wsu:Id = "WSHttpBinding_IQuery_2012_04_policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:TransportBinding xmlns:sp = "http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
<wsp:Policy>
<sp:TransportToken>
<wsp:Policy>
<sp:HttpsToken RequireClientCertificate = "false" />
</wsp:Policy>
</sp:TransportToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:Basic256 />
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Strict />
</wsp:Policy>
</sp:Layout>
<sp:IncludeTimestamp />
</wsp:Policy>
</sp:TransportBinding>
<sp:SignedSupportingTokens xmlns:sp = "http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
<wsp:Policy>
<sp:UsernameToken sp:IncludeToken = "http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:WssUsernameToken10 />
</wsp:Policy>
</sp:UsernameToken>
</wsp:Policy>
</sp:SignedSupportingTokens>
<sp:Wss11 xmlns:sp = "http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
<wsp:Policy />
</sp:Wss11>
<sp:Trust10 xmlns:sp = "http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
<wsp:Policy>
<sp:MustSupportIssuedTokens />
<sp:RequireClientEntropy />
<sp:RequireServerEntropy />
</wsp:Policy>
</sp:Trust10>
<wsaw:UsingAddressing />
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
Он генерирует правильную информацию о привязке/клиенте:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name = "WSHttpBinding_IQuery_2012_04">
<security mode = "TransportWithMessageCredential">
<transport clientCredentialType = "None" />
<message clientCredentialType = "UserName" establishSecurityContext = "false" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address = "https://somehost/ptsqamt/Maintain/Services/Data/2012/04/Query.svc"
binding = "wsHttpBinding" bindingConfiguration = "WSHttpBinding_IQuery_2012_04"
contract = "PTSQueryService.IQuery_2012_04" name = "WSHttpBinding_IQuery_2012_04" />
</client>
</system.serviceModel>
Сгенерированный клиентский прокси не создает ClientCredentials, поэтому я не могу добавить пользователя/пароль.
Сгенерированный фрагмент клиента для справки по .Net Framework:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
public partial class Query_2012_04Client : System.ServiceModel.ClientBase<WCFTest.PTSQueryService.IQuery_2012_04>, WCFTest.PTSQueryService.IQuery_2012_04 {
public Query_2012_04Client() {
}
public Query_2012_04Client(string endpointConfigurationName) :
base(endpointConfigurationName) {
}
public Query_2012_04Client(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public Query_2012_04Client(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public Query_2012_04Client(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress) {
}
public WCFTest.PTSQueryService.QueryParameter[] GetQueryParameters(int qtype, string qname) {
return base.Channel.GetQueryParameters(qtype, qname);
}
public System.Threading.Tasks.Task<WCFTest.PTSQueryService.QueryParameter[]> GetQueryParametersAsync(int qtype, string qname) {
return base.Channel.GetQueryParametersAsync(qtype, qname);
}
public string GetQueryData(int qtype, string qname, WCFTest.PTSQueryService.QueryArgument[] args, int page, int pageSize) {
return base.Channel.GetQueryData(qtype, qname, args, page, pageSize);
}
public System.Threading.Tasks.Task<string> GetQueryDataAsync(int qtype, string qname, WCFTest.PTSQueryService.QueryArgument[] args, int page, int pageSize) {
return base.Channel.GetQueryDataAsync(qtype, qname, args, page, pageSize);
}
}
Для справки, вот что сгенерировал проект .Net 8:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.2.0-preview1.23462.5")]
public partial class Query_2012_04Client : System.ServiceModel.ClientBase<PTSQueryService.IQuery_2012_04>, PTSQueryService.IQuery_2012_04
{
/// <summary>
/// Implement this partial method to configure the service endpoint.
/// </summary>
/// <param name = "serviceEndpoint">The endpoint to configure</param>
/// <param name = "clientCredentials">The client credentials</param>
static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
public Query_2012_04Client() :
base(Query_2012_04Client.GetDefaultBinding(), Query_2012_04Client.GetDefaultEndpointAddress())
{
this.Endpoint.Name = EndpointConfiguration.WSHttpBinding_IQuery_2012_04.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public Query_2012_04Client(EndpointConfiguration endpointConfiguration) :
base(Query_2012_04Client.GetBindingForEndpoint(endpointConfiguration), Query_2012_04Client.GetEndpointAddress(endpointConfiguration))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public Query_2012_04Client(EndpointConfiguration endpointConfiguration, string remoteAddress) :
base(Query_2012_04Client.GetBindingForEndpoint(endpointConfiguration), new System.ServiceModel.EndpointAddress(remoteAddress))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public Query_2012_04Client(EndpointConfiguration endpointConfiguration, System.ServiceModel.EndpointAddress remoteAddress) :
base(Query_2012_04Client.GetBindingForEndpoint(endpointConfiguration), remoteAddress)
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public Query_2012_04Client(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public System.Threading.Tasks.Task<PTSQueryService.QueryParameter[]> GetQueryParametersAsync(int qtype, string qname)
{
return base.Channel.GetQueryParametersAsync(qtype, qname);
}
public System.Threading.Tasks.Task<string> GetQueryDataAsync(int qtype, string qname, PTSQueryService.QueryArgument[] args, int page, int pageSize)
{
return base.Channel.GetQueryDataAsync(qtype, qname, args, page, pageSize);
}
public virtual System.Threading.Tasks.Task OpenAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginOpen(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndOpen));
}
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.WSHttpBinding_IQuery_2012_04))
{
System.ServiceModel.WSHttpBinding result = new System.ServiceModel.WSHttpBinding();
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
result.Security.Mode = System.ServiceModel.SecurityMode.TransportWithMessageCredential;
result.Security.Transport.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.None;
result.Security.Message.ClientCredentialType = System.ServiceModel.MessageCredentialType.UserName;
result.Security.Message.EstablishSecurityContext = false;
return result;
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.WSHttpBinding_IQuery_2012_04))
{
return new System.ServiceModel.EndpointAddress("https://somehost/ptsqamt/Maintain/Services/Data/2012/04/Query.s" +
"vc");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
private static System.ServiceModel.Channels.Binding GetDefaultBinding()
{
return Query_2012_04Client.GetBindingForEndpoint(EndpointConfiguration.WSHttpBinding_IQuery_2012_04);
}
private static System.ServiceModel.EndpointAddress GetDefaultEndpointAddress()
{
return Query_2012_04Client.GetEndpointAddress(EndpointConfiguration.WSHttpBinding_IQuery_2012_04);
}
public enum EndpointConfiguration
{
WSHttpBinding_IQuery_2012_04,
}
}
Спасибо за любую помощь, которую вы можете предложить.
@jdweng Проблема заключается в том, что при создании прокси-сервера не были добавлены ClientCredentials. Для клиента .Net Core это работало нормально.
ClientCredentials существует в System.ServiceModel.ClientBase <TChannel>.
Вы можете использовать его следующим образом:
Добавьте класс проверки на сервер: общедоступный класс CustomUserNameValidator: UserNamePasswordValidator {
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
if (userName != "admin" && password != "wcf.admin")
{
throw new System.IdentityModel.Tokens.SecurityTokenException("Unknown Username or Password");
}
}
}
Используйте этот код в клиенте, чтобы добавить имя пользователя и пароль:
using (var proxy = new ServiceReference1.Service1Client())
{
proxy.ClientCredentials.UserName.UserName = "admin";
proxy.ClientCredentials.UserName.Password = "wcf.admin";
string result = proxy.GetData(1);
Console.WriteLine(result);
var compositeObj = proxy.GetDataUsingDataContract(new CompositeType() { BoolValue = true, StringValue = "test" });
Console.WriteLine(SerializerToJson(compositeObj));
}
Console.ReadKey();
Если есть несоответствие, возникнет ошибка.
Я не использовал этот ответ, но он меня разбудил, так что я отдам вам должное за это. В отличие от версии Core, которая давала вам прямой доступ к ClientCredentials, мне пришлось привести ссылку на службу с помощью ClientBase. Это одна из тех деталей, которые забываешь, когда долго что-то не делал. Собираюсь добавить ответ
В конце концов, это было простое преобразование ссылки, которая предоставила ClientCredentials. Я как бы корю себя за то, что потратил на это так много времени.
Поэтому я на самом деле вызываю эту службу из службы. Я создаю ссылку в главном конструкторе сервисов:
public class MainService : IMainService
{
private readonly IQuery_2012_04 _ptsService;
public MainService()
{
_ptsService = new Query_2012_04Client();
((ClientBase<IQuery_2012_04>)_ptsService).ClientCredentials.UserName.UserName = ConfigurationManager.AppSettings["PTSServiceUser"];
((ClientBase<IQuery_2012_04>)_ptsService).ClientCredentials.UserName.Password = ConfigurationManager.AppSettings["PTSServicePwd"];
((ClientBase<IQuery_2012_04>)_ptsService).Endpoint.Address = new EndpointAddress(ConfigurationManager.AppSettings["PTSQueryServiceUrl"]);
}
*** Snip ***
}
Обычно вы передаете учетные данные пользователя, который вошел в систему клиента, на сервер через прокси. Вы не используете имя пользователя и пароль, поскольку пароль не зашифрован. Когда вы используете имя пользователя и пароль, вы создаете учетные данные (или используете учетные данные пользователя по умолчанию). См.: Learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/…