带有SSL和用户名和密码认证的自托管WCF服务
本文关键字:WCF 服务 认证 SSL 用户 密码 带有 | 更新日期: 2023-09-27 17:49:36
我想从控制台应用程序发布一个WCF服务。出于安全考虑,我希望通过SSL进行通信,因此我创建了一个自签名证书。对于身份验证,我编写了自己的UserNamePasswordValidator。不幸的是,这不起作用
这是我到目前为止的代码:
服务器public class Program
{
public static void Main()
{
var baseAddress = new Uri("https://localhost:8080/SelfHostedUsernamePasswordService");
using (var host = new ServiceHost(typeof(SelfHostedUsernamePasswordService), baseAddress))
{
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.Certificate;
var endpoint = host.AddServiceEndpoint(typeof(ISelfHostedUsernamePasswordService), binding, baseAddress);
var cf = new ChannelFactory<ISelfHostedUsernamePasswordService>(binding, endpoint.Address);
cf.Credentials.ClientCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.My,
X509FindType.FindByThumbprint,
"0000000000000000000000000000000000000000");
var metadataBehavior = new ServiceMetadataBehavior();
metadataBehavior.HttpsGetEnabled = true;
metadataBehavior.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(metadataBehavior);
var credentialBehavior = new ServiceCredentials();
credentialBehavior.UserNameAuthentication.CustomUserNamePasswordValidator = new UsernamePasswordValidator();
credentialBehavior.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
host.Description.Behaviors.Add(credentialBehavior);
host.Open();
Console.WriteLine("The service is ready at {0}", baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
host.Close();
}
}
}
public class UsernamePasswordValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (!string.Equals(userName, "admin", StringComparison.OrdinalIgnoreCase) ||
!string.Equals(password, "password", StringComparison.Ordinal))
{
Console.WriteLine("Validation failed.");
throw new SecurityTokenException("Validation failed.");
}
Console.WriteLine("Validation successful.");
}
}
class Program
{
static void Main()
{
using (var client = new SelfHostedUsernamePasswordServiceClient())
{
client.ClientCredentials.UserName.UserName = "admin";
client.ClientCredentials.UserName.Password = "password";
var result = client.GetData(12345);
Console.WriteLine("Result from service: {0}", result);
client.Close();
}
}
}
有了这个代码,我得到一个MessageSecurityException(不能找到一个令牌验证器的'System.IdentityModel.Tokens。UserNameSecurityToken的令牌类型)。但我认为创建TokenAuthenticator我走错了路…
顺便说一句,UsernamePasswordValidator从来没有被调用过。
好的,我知道了。
我必须将传输凭据类型设置为"证书",将消息凭据类型设置为"用户名"。在两边。
工作代码:
服务器public class Program
{
public static void Main()
{
var baseAddress = new Uri("https://localhost:8080/SelfHostedUsernamePasswordService");
using (var host = new ServiceHost(typeof(SelfHostedUsernamePasswordService), baseAddress))
{
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
var endpoint = host.AddServiceEndpoint(typeof(ISelfHostedUsernamePasswordService), binding, baseAddress);
var cf = new ChannelFactory<ISelfHostedUsernamePasswordService>(binding, endpoint.Address);
cf.Credentials.ClientCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.My,
X509FindType.FindByThumbprint,
"0000000000000000000000000000000000000000");
var credentialBehavior = new ServiceCredentials();
credentialBehavior.UserNameAuthentication.CustomUserNamePasswordValidator = new UsernamePasswordValidator();
credentialBehavior.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
credentialBehavior.IssuedTokenAuthentication.AllowUntrustedRsaIssuers = true;
host.Description.Behaviors.Add(credentialBehavior);
var metadataBehavior = new ServiceMetadataBehavior();
metadataBehavior.HttpsGetEnabled = true;
metadataBehavior.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(metadataBehavior);
host.Open();
Console.WriteLine("The service is ready at {0}", baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
host.Close();
}
}
}
public class UsernamePasswordValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (!string.Equals(userName, "admin", StringComparison.OrdinalIgnoreCase) ||
!string.Equals(password, "password", StringComparison.Ordinal))
{
Console.WriteLine("Validation failed.");
throw new SecurityTokenException("Validation failed.");
}
Console.WriteLine("Validation successful.");
}
}
class Program
{
static void Main()
{
var remoteAddress = new EndpointAddress(new Uri("https://localhost:8080/SelfHostedUsernamePasswordService"));
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
using (var client = new SelfHostedUsernamePasswordServiceClient(binding, remoteAddress))
{
client.ClientCredentials.UserName.UserName = "admin";
client.ClientCredentials.UserName.Password = "password";
var result = client.GetData(12345);
Console.WriteLine("Got result from service: {0}", result);
Console.ReadLine();
client.Close();
}
}
}
我需要TCP/IP协议,经过一周的努力工作,这工作得很好。o:)
查看完整的解决方案:
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Text;
namespace Demo.Services
{
public class TcpHostService
{
public const string CertificateName = "MyCertificateName";
public static ServiceHost GetServiceHost()
{
string tcpHost = GetTcpHost();
var portsharingBinding = new NetTcpBinding();
portsharingBinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
portsharingBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
portsharingBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;
var serviceHost = new ServiceHost(typeof(RemotingService), new Uri(tcpHost));
serviceHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
serviceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
serviceHost.AddServiceEndpoint(typeof(IRemote), portsharingBinding, tcpHost);
if (!File.Exists("Certificate.pfx"))
{
MakeCert();
}
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, CertificateName, false);
if (certificates == null || certificates.Count == 0)
{
InstallCert();
}
}
serviceHost.Credentials.ServiceCertificate.SetCertificate(
StoreLocation.CurrentUser, StoreName.My,
X509FindType.FindBySubjectName, CertificateName);
Console.WriteLine("Server escutando " + tcpHost);
return serviceHost;
}
private static void MakeCert()
{
var rsa = RSA.Create(2048);
var req = new CertificateRequest($"cn={CertificateName},OU=UserAccounts,DC=corp,DC=contoso,DC=com",
rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Parse("127.0.0.1"));
req.CertificateExtensions.Add(sanBuilder.Build());
var oidCollection = new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.2")
};
req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(oidCollection, true));
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation, false));
using (X509Certificate2 cert = req.CreateSelfSigned(DateTimeOffset.Now.AddDays(-10), DateTimeOffset.Now.AddYears(5)))
{
cert.FriendlyName = "JJConsulting Integration Certificate";
// Create PFX (PKCS #12) with private key
File.WriteAllBytes("Certificate.pfx", cert.Export(X509ContentType.Pfx, "pwd123"));
// Create Base 64 encoded CER (public key only)
File.WriteAllText("Certificate.cer",
"-----BEGIN CERTIFICATE-----'r'n"
+ Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)
+ "'r'n-----END CERTIFICATE-----");
}
}
public static void InstallCert()
{
using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
var cert = new X509Certificate2("Certificate.pfx", "pwd123", X509KeyStorageFlags.PersistKeySet);
store.Open(OpenFlags.ReadWrite);
store.Add(cert); //where cert is an X509Certificate object
}
}
private static string GetTcpHost()
{
return "net.tcp://localhost:5050/myservice1";
}
}
}
客户:private ChannelFactory<IRemote> GetChannelFactory()
{
var sTcp = "net.tcp://localhost:5050/myservice1"
var myBinding = new NetTcpBinding();
myBinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
myBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
myBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;
var endpointIdentity = EndpointIdentity.CreateDnsIdentity("MyCertificateName");
var myEndpoint = new EndpointAddress(new Uri(sTcp), endpointIdentity);
var factory = new ChannelFactory<IRemote>(myBinding, myEndpoint);
factory.Credentials.UserName.UserName = User;
factory.Credentials.UserName.Password = Password;
factory.Credentials.ServiceCertificate.SslCertificateAuthentication =
new X509ServiceCertificateAuthentication()
{
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck
};
return factory;
}
用户验证器:using System;
using System.IdentityModel.Selectors;
using System.ServiceModel;
namespace Demo.Auth
{
public class CustomUserNameValidator : UserNamePasswordValidator
{
// This method validates users. It allows in two users, test1 and test2
// with passwords 1tset and 2tset respectively.
// This code is for illustration purposes only and
// must not be used in a production environment because it is not secure.
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
if (!"user1".Equals(userName) || !"pwd".Equals(password))
{
throw new FaultException("Usuário ou senha inválido");
// When you do not want to throw an infomative fault to the client,
// throw the following exception.
// throw new SecurityTokenException("Unknown Username or Incorrect Password");
}
}
}
}