Asp.net 保护企业域中从DMZ到WCF服务的服务调用

Asp.net 保护企业域中从DMZ到WCF服务的服务调用,asp.net,.net,wcf,Asp.net,.net,Wcf,我们正在构建一个系统,该系统将在企业域上的IIS中托管大量WCF服务。DMZ中运行的表示层服务器将调用这些服务。需要保护对WCF服务的调用(即需要身份验证)。该系统是一个COTS系统,将部署到多个客户端站点 WCF支持使用Windows身份验证和现成的x.509证书对调用方进行身份验证。在这种情况下,由于DMZ表示层服务器将位于不同的域中,Windows身份验证将不适用于保护WCF服务 x、 509证书安全性是一个选项,在其他SO帖子(如下面的帖子)中也提到过: 我对x.509证书有两个顾虑:

我们正在构建一个系统,该系统将在企业域上的IIS中托管大量WCF服务。DMZ中运行的表示层服务器将调用这些服务。需要保护对WCF服务的调用(即需要身份验证)。该系统是一个COTS系统,将部署到多个客户端站点

WCF支持使用Windows身份验证和现成的x.509证书对调用方进行身份验证。在这种情况下,由于DMZ表示层服务器将位于不同的域中,Windows身份验证将不适用于保护WCF服务

x、 509证书安全性是一个选项,在其他SO帖子(如下面的帖子)中也提到过:

我对x.509证书有两个顾虑:

  • 表演。我自己还没有做过性能分析,但从其他人那里听说,验证x.509证书的开销可能使解决方案无法启动。我的下一个任务是对这一点进行性能分析

  • 易于部署。我过去发现,任何时候x.509证书出现在图片中,而不是SSL,都会给客户IT员工(采购、生成、管理)带来问题。这反过来又导致了我们产品的支持问题

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

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


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

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

    该解决方案有四个主要组件:

  • web层用于调用应用层上的服务的ServiceClientBase类
  • web层上的自定义配置部分,用于保存用户名/密码凭据,以便对应用层上的服务进行身份验证
  • 应用层上用于验证凭据的自定义UserNamePasswordValidator类
  • 应用层上的自定义配置部分,用于保存可用于身份验证的用户名/密码组合列表
  • 简化的ServiceClientBase类如下所示。可以修改if/else块以包括对您希望支持的任何绑定的支持。关于这个类需要指出的主要一点是,如果使用了安全性,并且客户端凭据类型是“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();
            }
        }
    }
    
    除了ServiceCredentialsSection类之外,还有一个CredentialsCollection类(扩展ConfigurationElementCollection)和一个CredentialsElement类(扩展ConfigurationElement)。我不会在这里包括CredentialsCollection类,因为它是一个很长的类,主要包含股票代码。您可以在Internet上找到ConfigurationElementCollection的参考实现,如。CredentialsElement类如下所示

    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];
            }
        }
    }
    
    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; }
        }
    }
    
    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;
            }
        }
    }
    
    // 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];
            }
        }
    }
    
    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节。可以加密此部分以保护凭据。有关加密.config文件部分的提示,请参阅

    <my.serviceCredentials>
        <credentials>
            <add serviceName="credentialKey" username="myusername" password="mypassword" />
        </credentials>
    </my.serviceCredentials>
    
    出于性能方面的原因,我们缓存了一组凭据,服务调用中包含的用户名/密码对将根据这些凭据进行验证。缓存类如下所示

    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];
            }
        }
    }
    
    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; }
        }
    }
    
    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;
            }
        }
    }
    
    // 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];
            }
        }
    }
    
    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; }
        }
    }
    
    最后一个难题是应用层上的自定义配置部分,用于保存用户名/密码组合列表。本节的代码如下所示

    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];
            }
        }
    }
    
    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; }
        }
    }
    
    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;
            }
        }
    }
    
    // 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];
            }
        }
    }
    
    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; }
        }
    }
    
    与前面一样,有一个自定义ConfigurationElementCollection类和一个自定义ConfigurationElement类。ConfigurationElement类如下所示

    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];
            }
        }
    }
    
    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; }
        }
    }
    
    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;
            }
        }
    }
    
    // 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];
            }
        }
    }
    
    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>
    
    
    
    希望这能帮助别人