我们正在构建一个系统,该系统将在IIS中托管许多WCF服务,这些服务位于企业域中。在DMZ中运行的表示层服务器将调用这些服务。对WCF服务的调用需要受到保护(即需要身份验证)。该系统是一个COTS系统,将部署到多个客户端站点。
WCF支持使用Windows身份验证和x.509证书对调用者进行身份验证。由于DMZ表示层服务器将位于不同的域中,Windows身份验证将无法在此场景中保护WCF服务。
x.509证书安全性是一个选项,在其他帖子中也提到过,如下所示:
Accessing WCF Service using TCP from the DMZ (not on network or domain)
我对x.509证书有两个顾虑:
出于上述原因,我正在考虑使用用户名/密码安全性来保护WCF调用。解决方案将使用自定义用户名/密码验证器。
https://msdn.microsoft.com/en-us/library/aa702565(v=vs.110).aspx
凭据将存储在DMZ中表示层服务器上的web.config文件的自定义部分中。相同的凭据将存储在应用层服务器上的web.config文件中。包含凭据的部分将在两个服务器上进行加密。
还有其他建议吗?对自定义用户名/密码验证器方法有什么想法吗?
发布于 2016-05-05 18:38:19
我们对各种选择做了很多测试。我们最终实现的解决方案是可配置的。它允许我们将用户名/密码安全性作为一个选项部署,或者返回到标准的安全方法,比如x.509证书,用于那些熟悉证书并能够管理它们的客户端。
该解决方案有四个主要组成部分:
简化的ServiceClientBase类如下所示。if/ you块可以被修改,以包含对任何您希望支持的绑定的支持。关于这个类,主要要指出的是,如果使用了安全性,并且客户端凭据类型是"username",那么我们将从.config文件加载用户名/密码。否则,我们回过头来使用标准WCF安全配置。
public class ServiceClientBase<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
{
public const string AppTierServiceCredentialKey = "credentialKey";
public ServiceClientBase()
{
bool useUsernameCredentials = false;
Binding binding = this.Endpoint.Binding;
if (binding is WSHttpBinding)
{
WSHttpBinding wsHttpBinding = (WSHttpBinding)binding;
if (wsHttpBinding.Security != null && wsHttpBinding.Security.Mode == SecurityMode.TransportWithMessageCredential)
{
if (wsHttpBinding.Security.Message != null && wsHttpBinding.Security.Message.ClientCredentialType == MessageCredentialType.UserName)
{
useUsernameCredentials = true;
}
}
}
else if (binding is BasicHttpBinding)
{
BasicHttpBinding basicHttpBinding = (BasicHttpBinding)binding;
if (basicHttpBinding.Security != null && basicHttpBinding.Security.Mode == BasicHttpSecurityMode.TransportWithMessageCredential)
{
if (basicHttpBinding.Security.Message != null && basicHttpBinding.Security.Message.ClientCredentialType == BasicHttpMessageCredentialType.UserName)
{
useUsernameCredentials = true;
}
}
}
...
if (useUsernameCredentials)
{
ServiceCredentialsSection section = (ServiceCredentialsSection)ConfigurationManager.GetSection(ServiceCredentialsSection.SectionName);
CredentialsElement credentials = section.Credentials[AppTierServiceCredentialKey];
this.ClientCredentials.UserName.UserName = credentials.UserName;
this.ClientCredentials.UserName.Password = credentials.Password;
}
}
// http://blogs.msdn.com/b/jjameson/archive/2010/03/18/avoiding-problems-with-the-using-statement-and-wcf-service-proxies.aspx
void IDisposable.Dispose()
{
if (this.State == CommunicationState.Faulted)
{
this.Abort();
}
else if (this.State != CommunicationState.Closed)
{
this.Close();
}
}
}凭据的自定义配置节类如下所示。
public class ServiceCredentialsSection : ConfigurationSection
{
public const string SectionName = "my.serviceCredentials";
public const string CredentialsTag = "credentials";
[ConfigurationProperty(CredentialsTag, IsDefaultCollection = false)]
[ConfigurationCollection(typeof(CredentialsCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
public CredentialsCollection Credentials
{
get
{
return (CredentialsCollection)this[CredentialsTag];
}
}
}除了ServiceCredentialsSection类之外,还有一个CredentialsCollection类(扩展ConfigurationElementCollection)和一个CredentialsElement类(扩展ConfigurationElement)。这里不包括CredentialsCollection类,因为它是一个很长的类,主要是股票代码。您可以在互联网上找到ConfigurationElementCollection的参考实现,比如在https://msdn.microsoft.com/en-us/library/system.configuration.configurationelementcollection(v=vs.110).aspx。CredentialsElement类如下所示。
public class CredentialsElement : ConfigurationElement
{
[ConfigurationProperty("serviceName", IsKey = true, DefaultValue = "", IsRequired = true)]
public string ServiceName
{
get { return base["serviceName"] as string; }
set { base["serviceName"] = value; }
}
[ConfigurationProperty("username", DefaultValue = "", IsRequired = true)]
public string UserName
{
get { return base["username"] as string; }
set { base["username"] = value; }
}
[ConfigurationProperty("password", DefaultValue = "", IsRequired = true)]
public string Password
{
get { return base["password"] as string; }
set { base["password"] = value; }
}
}上面提到的类支持一个.config部分,如下所示。本节可以加密以确保凭据的安全。有关加密.config文件的一节的技巧,请参见.config。
<my.serviceCredentials>
<credentials>
<add serviceName="credentialKey" username="myusername" password="mypassword" />
</credentials>
</my.serviceCredentials>第三块拼图是定制的UserNamePasswordValidator。这个类的代码如下所示。
public class PrivateServiceUserNamePasswordValidator : UserNamePasswordValidator
{
private IPrivateServiceAccountCache _accountsCache;
public IPrivateServiceAccountCache AccountsCache
{
get
{
if (_accountsCache == null)
{
_accountsCache = ServiceAccountsCache.Instance;
}
return _accountsCache;
}
}
public override void Validate(string username, string password)
{
if (!(AccountsCache.Validate(username, password)))
{
throw new FaultException("Unknown Username or Incorrect Password");
}
}
}出于性能原因,我们缓存服务调用中包含的用户名/密码对将根据的凭证集进行验证。缓存类如下所示。
public class ServiceAccountsCache : IPrivateServiceAccountCache
{
private static ServiceAccountsCache _instance = new ServiceAccountsCache();
private Dictionary<string, ServiceAccount> _accounts = new Dictionary<string, ServiceAccount>();
private ServiceAccountsCache() { }
public static ServiceAccountsCache Instance
{
get
{
return _instance;
}
}
public void Add(ServiceAccount account)
{
lock (_instance)
{
if (account == null) throw new ArgumentNullException("account");
if (String.IsNullOrWhiteSpace(account.Username)) throw new ArgumentException("Username cannot be null for a service account. Set the username attribute for the service account in the my.serviceAccounts section in the web.config file.");
if (String.IsNullOrWhiteSpace(account.Password)) throw new ArgumentException("Password cannot be null for a service account. Set the password attribute for the service account in the my.serviceAccounts section in the web.config file.");
if (_accounts.ContainsKey(account.Username.ToLower())) throw new ArgumentException(String.Format("The username '{0}' being added to the service accounts cache already exists. Verify that the username exists only once in the my.serviceAccounts section in the web.config file.", account.Username));
_accounts.Add(account.Username.ToLower(), account);
}
}
public bool Validate(string username, string password)
{
if (username == null) throw new ArgumentNullException("username");
string key = username.ToLower();
if (_accounts.ContainsKey(key) && _accounts[key].Password == password)
{
return true;
}
else
{
return false;
}
}
}上面的缓存在应用程序启动时在Global.Application_Start方法中初始化,如下所示。
// Cache service accounts.
ServiceAccountsSection section = (ServiceAccountsSection)ConfigurationManager.GetSection(ServiceAccountsSection.SectionName);
if (section != null)
{
foreach (AccountElement account in section.Accounts)
{
ServiceAccountsCache.Instance.Add(new ServiceAccount() { Username = account.UserName, Password = account.Password, AccountType = (ServiceAccountType)Enum.Parse(typeof(ServiceAccountType), account.AccountType, true) });
}
}拼图的最后一部分是应用程序层上用于保存用户名/密码组合列表的自定义配置部分。本节的代码如下所示。
public class ServiceAccountsSection : ConfigurationSection
{
public const string SectionName = "my.serviceAccounts";
public const string AccountsTag = "accounts";
[ConfigurationProperty(AccountsTag, IsDefaultCollection = false)]
[ConfigurationCollection(typeof(AccountsCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
public AccountsCollection Accounts
{
get
{
return (AccountsCollection)this[AccountsTag];
}
}
}和前面一样,有一个自定义ConfigurationElementCollection类和一个自定义ConfigurationElement类。ConfigurationElement类如下所示。
public class AccountElement : ConfigurationElement
{
[ConfigurationProperty("username", IsKey = true, DefaultValue = "", IsRequired = true)]
public string UserName
{
get { return base["username"] as string; }
set { base["username"] = value; }
}
[ConfigurationProperty("password", DefaultValue = "", IsRequired = true)]
public string Password
{
get { return base["password"] as string; }
set { base["password"] = value; }
}
[ConfigurationProperty("accountType", DefaultValue = "", IsRequired = true)]
public string AccountType
{
get { return base["accountType"] as string; }
set { base["accountType"] = value; }
}
}这些配置类支持.config文件XML片段,如下所示。和以前一样,这个部分可以被加密。
<my.serviceAccounts>
<accounts>
<add username="myusername" password="mypassword" accountType="development" />
</accounts>
</my.serviceAccounts>希望这能帮上忙。
https://stackoverflow.com/questions/35295883
复制相似问题