首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在企业域中保护从DMZ到WCF服务的服务调用

在企业域中保护从DMZ到WCF服务的服务调用
EN

Stack Overflow用户
提问于 2016-02-09 15:30:57
回答 1查看 580关注 0票数 2

我们正在构建一个系统,该系统将在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证书有两个顾虑:

  1. 性能。我自己还没有做过性能分析,但是从其他人那里听说,验证x.509证书的开销可能会使解决方案成为不可能的。我的下一个任务是对这一点进行性能分析。
  2. 很容易部署。我在过去发现,任何时候,除了SSL之外,任何时候x.509证书都会给客户IT人员带来问题(采购、生成、管理)。这反过来又导致了对我们产品的支持问题。

出于上述原因,我正在考虑使用用户名/密码安全性来保护WCF调用。解决方案将使用自定义用户名/密码验证器。

https://msdn.microsoft.com/en-us/library/aa702565(v=vs.110).aspx

凭据将存储在DMZ中表示层服务器上的web.config文件的自定义部分中。相同的凭据将存储在应用层服务器上的web.config文件中。包含凭据的部分将在两个服务器上进行加密。

还有其他建议吗?对自定义用户名/密码验证器方法有什么想法吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2016-05-05 18:38:19

我们对各种选择做了很多测试。我们最终实现的解决方案是可配置的。它允许我们将用户名/密码安全性作为一个选项部署,或者返回到标准的安全方法,比如x.509证书,用于那些熟悉证书并能够管理它们的客户端。

该解决方案有四个主要组成部分:

  1. web层用于调用应用程序层上的服务的ServiceClientBase类。
  2. web层上的自定义配置部分,用于保存用户名/密码凭据,用于对应用程序层上的服务进行身份验证。
  3. 应用程序层上用于验证凭据的自定义UserNamePasswordValidator类。
  4. 应用程序层上的自定义配置部分,用于保存可用于身份验证的用户名/密码组合列表。

简化的ServiceClientBase类如下所示。if/ you块可以被修改,以包含对任何您希望支持的绑定的支持。关于这个类,主要要指出的是,如果使用了安全性,并且客户端凭据类型是"username",那么我们将从.config文件加载用户名/密码。否则,我们回过头来使用标准WCF安全配置。

代码语言:javascript
复制
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();
        }
    }
}

凭据的自定义配置节类如下所示。

代码语言:javascript
复制
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类如下所示。

代码语言:javascript
复制
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。

代码语言:javascript
复制
<my.serviceCredentials>
    <credentials>
        <add serviceName="credentialKey" username="myusername" password="mypassword" />
    </credentials>
</my.serviceCredentials>

第三块拼图是定制的UserNamePasswordValidator。这个类的代码如下所示。

代码语言:javascript
复制
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");
        }
    }
}

出于性能原因,我们缓存服务调用中包含的用户名/密码对将根据的凭证集进行验证。缓存类如下所示。

代码语言:javascript
复制
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方法中初始化,如下所示。

代码语言:javascript
复制
// 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) });
    }
}

拼图的最后一部分是应用程序层上用于保存用户名/密码组合列表的自定义配置部分。本节的代码如下所示。

代码语言:javascript
复制
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类如下所示。

代码语言:javascript
复制
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片段,如下所示。和以前一样,这个部分可以被加密。

代码语言:javascript
复制
<my.serviceAccounts>
    <accounts>
        <add username="myusername" password="mypassword" accountType="development" />
    </accounts>
</my.serviceAccounts>

希望这能帮上忙。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/35295883

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档