带有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从来没有被调用过。

带有SSL和用户名和密码认证的自托管WCF服务

好的,我知道了。

我必须将传输凭据类型设置为"证书",将消息凭据类型设置为"用户名"。在两边。

工作代码:

服务器

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");
            }
        }
    }
}