private string ReplaceJsonNode(string src, string encryptedDataKey, bool isEnc = true) { // 示例配置用的是JSON,如果用的是 yaml,这里换成用 yaml 解析即可。 var jObj = JObject.Parse(src); foreach (var item in _jsonPaths) { var t = jObj.SelectToken(item); if (t != null) { var r = t.ToString(); // 加解密 var newToken = isEnc ? AESEncrypt(r, encryptedDataKey) : AESDecrypt(r, encryptedDataKey); if (!string.IsNullOrWhiteSpace(newToken)) { // 替换旧值 t.Replace(newToken); } } } return jObj.ToString(); }
到这里,自定义的 ConfigFilter 已经完成了,下面就是真正的应用了。
简单应用老样子,建一个 WebApi 项目,添加自定义 ConfigFilter 所在的包/项目/程序集。
修改 appsettings.json
{ "NacosConfig": { "Listeners": [ { "Optional": true, "DataId": "demo", "Group": "DEFAULT_GROUP" } ], "Namespace": "cs", "ServerAddresses": [ "http://localhost:8848/" ], "ConfigFilterAssemblies": [ "XXXX.CusLib" ], "ConfigFilterExtInfo": "{\"JsonPaths\":[\"ConnectionStrings.Default\"],\"Other\":\"xxxxxx\"}" } }
注:老黄这里把 Optional 设置成 true,是为了第一次运行的时候,如果服务端没有进行配置而不至于退出程序。
修改 Program.cs
public class Program { public static void Main(string[] args) { var outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}"; Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("System", LogEventLevel.Warning) .MinimumLevel.Debug() .WriteTo.Console(outputTemplate: outputTemplate) .CreateLogger(); System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); try { Log.ForContext<Program>().Information("Application starting..."); CreateHostBuilder(args, Log.Logger).Build().Run(); } catch (System.Exception ex) { Log.ForContext<Program>().Fatal(ex, "Application start-up failed!!"); } finally { Log.CloseAndFlush(); } } public static IHostBuilder CreateHostBuilder(string[] args, Serilog.ILogger logger) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, builder) => { var c = builder.Build(); builder.AddNacosV2Configuration(c.GetSection("NacosConfig"), logAction: x => x.AddSerilog(logger)); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>().UseUrls("http://*:8787"); }) .UseSerilog(); }
最后是 Startup.cs
public class Startup { // 省略部分.... public void ConfigureServices(IServiceCollection services) { services.AddNacosV2Config(Configuration, null, "NacosConfig"); services.Configure<AppSettings>(Configuration.GetSection("AppSettings")); services.AddControllers(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { var configSvc = app.ApplicationServices.GetRequiredService<Nacos.V2.INacosConfigService>(); var db = $"demo-{DateTimeOffset.Now.ToString("yyyyMMdd_HHmmss")}"; var oldConfig = "{\"ConnectionStrings\":{\"Default\":\"Server=127.0.0.1;Port=3306;Database=" + db + ";User Id=app;Password=098765;\"},\"version\":\"测试version---\",\"AppSettings\":{\"Str\":\"val\",\"num\":100,\"arr\":[1,2,3,4,5],\"subobj\":{\"a\":\"" + db + "\"}}}"; configSvc.PublishConfig("demo", "DEFAULT_GROUP", oldConfig).ConfigureAwait(false).GetAwaiter().GetResult(); var options = app.ApplicationServices.GetRequiredService<IOptionsMonitor<AppSettings>>(); Console.WriteLine("===用 IOptionsMonitor 读取配置==="); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(options.CurrentValue)); Console.WriteLine(""); Console.WriteLine("===用 IConfiguration 读取配置==="); Console.WriteLine(Configuration["ConnectionStrings:Default"]); Console.WriteLine(""); var pwd = $"demo-{new Random().Next(100000, 999999)}"; var newConfig = "{\"ConnectionStrings\":{\"Default\":\"Server=127.0.0.1;Port=3306;Database="+ db + ";User Id=app;Password="+ pwd +";\"},\"version\":\"测试version---\",\"AppSettings\":{\"Str\":\"val\",\"num\":100,\"arr\":[1,2,3,4,5],\"subobj\":{\"a\":\""+ db +"\"}}}"; // 模拟 配置变更 configSvc.PublishConfig("demo", "DEFAULT_GROUP", newConfig).ConfigureAwait(false).GetAwaiter().GetResult(); System.Threading.Thread.Sleep(500); var options2 = app.ApplicationServices.GetRequiredService<IOptionsMonitor<AppSettings>>(); Console.WriteLine("===用 IOptionsMonitor 读取配置==="); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(options2.CurrentValue)); Console.WriteLine(""); Console.WriteLine("===用 IConfiguration 读取配置==="); Console.WriteLine(Configuration["ConnectionStrings:Default"]); Console.WriteLine(""); // 省略部分.... } }
最后来看看几张效果图:
首先是程序的运行日志。
其次是和 Nacos 控制台的对比。
到这里的话,基于 Nacos 的加解密就完成了。
写在最后敏感配置项的加解密还是很有必要的,配置中心负责存储,客户端负责加解密,这样的方式可以让用户更加灵活的选择自己想要的加解密方法。
本文的示例代码已经上传到 Github,仅供参考。
https://github.com/catcherwong-archive/2021/tree/main/NacosConfigWithEncryption
最后的最后,希望感兴趣的大佬可以一起参与到这个项目来。
nacos-sdk-csharp 的地址 :https://github.com/nacos-group/nacos-sdk-csharp