大家好,我是Edison。
最近,在使用CAP事件总线时,碰到了这样一个需求:微服务采用的是MongoDB,而且还是带身份验证 和 SSL根证书验证的。由于目前网上能找到的资料,都是不带身份验证的MongoDB,现在网络信息安全越来越被重视,那么就需要自己研究一番了。
CAP.MongoDB组件
CAP是一个开源的事件总线项目,在.NET社区已经十分流行了,它提供了多种存储方式:MSSQL, MySQL, PgSQL,MongoDB等,这里我们主要关注MongoDB。快速安装CAP.MongoDB组件:
PM> Install-Package DotNetCore.CAP.MongoDB
快速集成CAP.MongoDB组件(StartUp.cs):
public void ConfigureServices(IServiceCollection services) { // ... services.AddCap(x => { x.UseMongoDB(opt=>{ //MongoDBOptions }); // x.UseXXX ... }); }
目前CAP提供的Options如下:
也就是说,CAP的Option只提供了一个DatabaseConnection选项,让我们自己构造Mongo链接字符串供CAP使用。
那么,我们就需要准确地构造这个Mongo连接字符串了。
这里,我们以之前分享的一篇文章《在ASP.NET 6中使用工作单元操作MongoDB》为基础,不熟悉的朋友可以先看看这篇文章。
前提条件/准备工作
这里我们假设在appsettings中我们针对MongoDB的配置项如下格式:
"MongoDatabaseConfigs": { "Servers": "xxx01.server.net,xxx02.server.net,xxx03.server.net", "Port": 27017, // optional, default : 27017 "ReplicaSetName": "myrs", "DatabaseName": "TEST_DB", "AuthDatabaseName": "admin", // optional, default: admin "ApplicationName": "Todo", "UserName": "test_dev_user", "Password": "test_dev_password", "UseTLS": true, // optional, default : false "AllowInsecureTLS": true, // optional, default : true "SslCertificatePath": "/etc/pki/tls/certs/MyCustomCA.cer" }
配置项中给出了UserName、Password 还有 SSL证书的路径,这些都是需要构造到连接字符串中的。当然,在Kubernetes中,都建议放到Secret中去。
核心工作:封装构造连接字符串的方法
这里我们封装一个生成MongoDB连接字符串的静态方法,用于读取appsettings中的配置项,并帮我们生成CAP可以用的MongoDB连接字符串:
public static class DbConnUtil { // Const Settings for Mongo private const int DEFAULT_CONNECT_TIMEOUT_MS = 10000; // 10s private const int DEFAULT_SERVER_SELECTION_TIMEOUT_MS = 5000; // 5s private const string DEFAULT_AUTH_MECHANISM = "SCRAM-SHA-256"; // SCRAM-SHA-256 private const string DEFAULT_READ_PREFERENCE = "primaryPreferred"; // Primary Preferred private const string DEFAULT_SSL_INVALID_HOSTNAME_ALLOWED = "true"; // Allow Invalid HostName for SSL /// <summary> /// 获取MongoDB数据库连接字符串 /// 需要在配置文件中提前根据指定Key进行设置 /// </summary> public static string GetMongoDbConnectionString(IConfiguration config) { var servers = config["MongoDatabaseConfigs:Servers"]; var port = config["MongoDatabaseConfigs:Port"] ?? "27017"; if (string.IsNullOrWhiteSpace(servers)) throw new ArgumentNullException("Mongo Servers Configuration is Missing!"); var mongoServers = servers.Split(','); // Basic Auth var userName = config["MongoDatabaseConfigs:UserName"]; var password = config["MongoDatabaseConfigs:Password"]; if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(password)) throw new ArgumentNullException("Mongo Account Configuration is Missing!"); // Uri var replicaName = config["MongoDatabaseConfigs:ReplicaSetName"]; var authDatabaseName = config["MongoDatabaseConfigs:AuthDatabaseName"] ?? "admin"; var mongoUriBuilder = new StringBuilder(); mongoUriBuilder.Append($"mongodb://{userName}:{password}@"); for (int i = 0; i < mongoServers.Length; i++) { if (i < mongoServers.Length - 1) { mongoUriBuilder.Append($"{mongoServers[i]}:{port},"); } else { mongoUriBuilder.Append($"{mongoServers[i]}:{port}/?"); } } // Settings var applicationName = config["MongoDatabaseConfigs:ApplicationName"]; mongoUriBuilder.Append($"replicaSet={replicaName}"); mongoUriBuilder.Append($"&appName={applicationName}"); mongoUriBuilder.Append($"&authSource={authDatabaseName}"); mongoUriBuilder.Append($"&authMechanism={DEFAULT_AUTH_MECHANISM}"); mongoUriBuilder.Append($"&connectTimeoutMS={DEFAULT_CONNECT_TIMEOUT_MS}"); mongoUriBuilder.Append($"&serverSelectionTimeoutMS={DEFAULT_SERVER_SELECTION_TIMEOUT_MS}"); mongoUriBuilder.Append($"&readPreference={DEFAULT_READ_PREFERENCE}"); // TLS/SSL Auth var useTLS = Convert.ToBoolean(config["MongoDatabaseConfigs:UseTLS"] ?? "false"); if (useTLS) { var allowInsecureTls = Convert.ToBoolean(config["MongoDatabaseConfigs:AllowInsecureTLS"] ?? "true"); var sslCertificatePath = config["MongoDatabaseConfigs:SslCertificatePath"]; mongoUriBuilder.Append($"&ssl={useTLS}"); mongoUriBuilder.Append($"&net.ssl.CAFile={sslCertificatePath}"); mongoUriBuilder.Append($"&net.ssl.allowInvalidCertificates={DEFAULT_SSL_INVALID_HOSTNAME_ALLOWED}"); } return mongoUriBuilder.ToString(); } }
最终可以生成的连接字符串为:
mongodb://test_dev_user:test_dev_password@xxx01:27017.server.net,xxx02.server.net:27017,xxx03.server.net:27017/?replicaSet=myrs&appName=Todo&authSource=admin&authMechanism=SCRAM-SHA-256&connectTimeoutMS=10000&serverSelectionTimeoutMS=5000&readPreference=primaryPreferred&ssl=True&net.ssl.CAFile=/etc/pki/tls/certs/MyCustomCA.cer&net.ssl.allowInvalidCertificates=true
ASP.NET Core集成CAP
这里我们使用刚刚封装的方法来生成Mongo连接字符串,来快速集成CAP:
public static IServiceCollection AddApplicationEventBus(this IServiceCollection services, IConfiguration config) { ...... // CAP EventBus services.AddCap(option => { // Transport option.UseKafka(option => { option.Servers = config["EventBusConfigs:KafkaServers"] ?? throw new ArgumentException("EventBusConfigs:KafkaServers must be set!"); option.ConnectionPoolSize = int.Parse(config["EventBusConfigs:CapConnectionPoolSize"] ?? ApplicationDefaultSettings.Default_ConnectionPool_Size); option.CustomHeaders = e => new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>(Headers.MessageId, SnowflakeId.Default().NextId().ToString()), new KeyValuePair<string, string>(Headers.MessageName, e.Topic) }; if (Convert.ToBoolean(config["EventBusConfigs:EnableAuthorization"] ?? "false")) { var userName = config["EventBusConfigs:SaslUserName"]; var passWord = config["EventBusConfigs:SaslPassword"]; if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(passWord)) throw new ArgumentNullException("Kafka username or password can't be null!"); option.MainConfig.Add(KafkaMainConfigKey.SECURITY_PROTOCOL, KafkaProtocol.SASL_SSL); option.MainConfig.Add(KafkaMainConfigKey.SASL_MECHANISM, KafkaAuthMechanism.PLAIN); option.MainConfig.Add(KafkaMainConfigKey.SASL_USERNAME, userName); option.MainConfig.Add(KafkaMainConfigKey.SASL_PASSWORD, passWord); if (!string.IsNullOrWhiteSpace(config["EventBusConfigs:SslCertificatePath"])) option.MainConfig.Add(KafkaMainConfigKey.SSL_CA_LOCATION, config["EventBusConfigs:SslCertificatePath"]); if (!string.IsNullOrWhiteSpace(config["EventBusConfigs:EnableSslCertificateVerification"])) option.MainConfig.Add(KafkaMainConfigKey.ENABLE_SSL_CERT_VERIFICATION, config["EventBusConfigs:EnableSslCertificateVerification"]); } }); option.SucceedMessageExpiredAfter = 3600 * 24 * int.Parse(config["EventBusConfigs:CapSuccessMsgExpireDays"]; // Storage option.UseMongoDB(option => { option.DatabaseConnection = DbConnUtil.GetMongoDbConnectionString(config); option.DatabaseName = config["MongoDatabaseConfigs:DatabaseName"] ?? throw new ArgumentException("MongoDatabaseConfigs:DatabaseName must be set!"); option.PublishedCollection = "msg.published"; option.ReceivedCollection = "msg.received"; }); }); ...... return services; }
小结
本文我们了解了如何在CAP中集成带基础身份验证(用户名/密码)+SSL根证书验证的MongoDB,方便CAP能够正常连接MongoDB并生成本地消息表,在网络信息安全越来越重视的现在,相信会对你使用CAP+MongoDB有一定帮助!
参考资料
CAP官方文档:https://cap.dotnetcore.xyz/user-guide/en/storage/mongodb/
MongoDB官方文档:https://www.mongodb.com/docs/v5.0/security/