.NET Core結合Nacos實現(xiàn)配置加解密的方法
背景
當我們把應用的配置都放到配置中心后,很多人會想到這樣一個問題,配置里面有敏感的信息要怎么處理呢?
信息既然敏感的話,那么加個密就好了嘛,相信大部分人的第一感覺都是這個,確實這個是最簡單也是最合適的方法。
其實很多人都在關注這個問題,好比說,數(shù)據(jù)庫的連接字符串,調用第三方的密鑰等等這些信息,都是不太想讓很多人知道的。
那么如果我們把配置放在 Nacos 了,我們可以怎么操作呢?
想了想不外乎這么幾種:
- 全部服務端搞定,客戶端只管取;
- 全部客戶端搞定,服務端只管存;
- 客戶端為主,服務端為輔,服務端存一些加解密需要的輔助信息即可。
有一個老哥已經(jīng)在 issue 里面提出了相關的落地方案,也包含了部分實現(xiàn)。
https://github.com/alibaba/nacos/issues/5367
簡要概述的話就是,開個口子,用戶可以在客戶端拓展任意加解密方式,同時服務端可以輔助這一操作。
不過看了 2.0.2 的代碼,服務端這一塊的“輔助”還未完成,不過對客戶端來說,這一塊其實問題已經(jīng)不大了。
6月14號發(fā)布的 nacos-sdk-csharp 1.1.0 版本已經(jīng)支持了這一功能
下面就用 .NET 5 和 Nacos 2.0.2 為例,來簡單說明一下。
簡單原理說明
sdk 里面在進行配置相關讀寫操作的時候,會有一個 DoFilter 的操作。這個操作就是我們的切入點。
既然要執(zhí)行 Filter , 那么執(zhí)行的 Filter 從那里來呢? 答案是 IConfigFilter 。
sdk 里面提供了 IConfigFilter 這個接口,但是不提供實現(xiàn),具體實現(xiàn)交由用戶自定義,畢竟 100 個人就有 100 種不一樣的實現(xiàn)。
下面看看它的定義。
public interface IConfigFilter
{
void Init(NacosSdkOptions options);
int GetOrder();
string GetFilterName();
void DoFilter(IConfigRequest request, IConfigResponse response, IConfigFilterChain filterChain);
}
Init 方法就是對這個 ConfigFilter 進行一些初始化操作,好比說從 Options 里面拿一些額外的信息。
GetOrder 和 GetFilterName 屬于輔助信息,指定這個 ConfigFilter 的執(zhí)行順序(越小越先執(zhí)行)和名稱。
DoFilter 就是核心了,它可以變更 request 和 response ,這兩個對象內部都會維護一個包含配置信息的 Dictionary。
換言之,只要我們定義一個 ConfigFilter,實現(xiàn)了這個接口,那么配置想怎么操作都可以了,加解密就是小問題了。
其中 NacosSdkOptions 里面加了兩個配置項,是專門給這個功能用的 ConfigFilterAssemblies 和 ConfigFilterExtInfo
ConfigFilterAssemblies 是自定義 ConfigFilter 所在的程序集的名字,這里是一個字符串列表類型的參數(shù),sdk 會根據(jù)這個名字去找到對應的實現(xiàn),然后初始化好。
ConfigFilterExtInfo 是實現(xiàn) ConfigFilter 是需要用到的擴展信息,這里是一個字符串類型的參數(shù),擴展信息復雜的可以考慮傳入一個 JSON 字符串。
下面來看個具體的例子吧。
自定義 ConfigFilter
這個 Filter 實現(xiàn)的效果是把部分敏感配置項進行加密,敏感的配置項需要在配置文件中指定。
先是 Init 方法:
public void Init(NacosSdkOptions options)
{
// 從 Options 里面的拓展信息獲取需要加密的 json path
// 這里只是示例,根據(jù)具體情況調整成自己合適的?。。?!
var extInfo = JObject.Parse(options.ConfigFilterExtInfo);
if (extInfo.ContainsKey("JsonPaths"))
{
// JsonPaths 在這里的含義是,那個path下面的內容要加密
_jsonPaths = extInfo.GetValue("JsonPaths").ToObject<List<string>>();
}
}
然后是 DoFilter 方法:
這個方法里面要注意幾點:
- request 只有請求的時候才會有值,其他時候都是 null 值。
- response 只有響應的時候才會有值,其他時候都是 null 值。
- 操作完之后,一定要調用 PutParameter 方法進行覆蓋才會生效。
public void DoFilter(IConfigRequest request, IConfigResponse response, IConfigFilterChain filterChain)
{
if (request != null)
{
var encryptedDataKey = DefaultKey;
var raw_content = request.GetParameter(Nacos.V2.Config.ConfigConstants.CONTENT);
// 部分配置加密后的 content
var content = ReplaceJsonNode((string)raw_content, encryptedDataKey, true);
// 加密配置后,不要忘記更新 request !!!!
request.PutParameter(Nacos.V2.Config.ConfigConstants.ENCRYPTED_DATA_KEY, encryptedDataKey);
request.PutParameter(Nacos.V2.Config.ConfigConstants.CONTENT, content);
}
if (response != null)
{
var resp_content = response.GetParameter(Nacos.V2.Config.ConfigConstants.CONTENT);
var resp_encryptedDataKey = response.GetParameter(Nacos.V2.Config.ConfigConstants.ENCRYPTED_DATA_KEY);
// nacos 2.0.2 服務端目前還沒有把 encryptedDataKey 記錄并返回,所以 resp_encryptedDataKey 目前只會是 null
// 如果服務端有記錄并且能返回,我們可以做到每一個配置都用不一樣的 encryptedDataKey 來加解密。
// 目前的話,只能固定一個 encryptedDataKey
var encryptedDataKey = (resp_encryptedDataKey == null || string.IsNullOrWhiteSpace((string)resp_encryptedDataKey))
? DefaultKey
: (string)resp_encryptedDataKey;
var content = ReplaceJsonNode((string)resp_content, encryptedDataKey, false);
response.PutParameter(Nacos.V2.Config.ConfigConstants.CONTENT, content);
}
}
這里涉及 encryptedDataKey 的相關操作都只是預留操作,現(xiàn)階段可以不用理會。
還有一個 ReplaceJsonNode 方法就是替換敏感配置的具體操作了。
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 已經(jīng)完成了,下面就是真正的應用了。
簡單應用
老樣子,建一個 WebApi 項目,添加自定義 ConfigFilter 所在的包/項目/程序集。
這里用的是集成 ASP.NET Core 的例子。
修改 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 的加解密就完成了。
寫在最后
敏感配置項的加解密還是很有必要的,配置中心負責存儲,客戶端負責加解密,這樣的方式可以讓用戶更加靈活的選擇自己想要的加解密方法。
本文的示例代碼已經(jīng)上傳到 Github,僅供參考。
https://github.com/catcherwong-archive/2021/tree/main/NacosConfigWithEncryption
最后的最后,希望感興趣的大佬可以一起參與到這個項目來。
nacos-sdk-csharp 的地址 :https://github.com/nacos-group/nacos-sdk-csharp
到此這篇關于.NET Core結合Nacos實現(xiàn)配置加解密的方法的文章就介紹到這了,更多相關.NET Core Nacos配置加解密內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- .NET Core Windows環(huán)境安裝配置教程
- ASP.NET core Web中使用appsettings.json配置文件的方法
- 詳解ASP.NET Core實現(xiàn)強類型Configuration讀取配置數(shù)據(jù)
- .NET Core簡單讀取json配置文件
- ASP.NET Core 2.0 WebApi全局配置及日志實例
- .net core下配置訪問數(shù)據(jù)庫操作
- .NET Core讀取配置文件方式詳細總結
- ASP.NET Core配置教程之讀取配置信息
- 詳解ASP.NET Core 在 JSON 文件中配置依賴注入
- 如何在ASP.NET Core類庫項目中讀取配置文件詳解
- asp.net Core3.0區(qū)域與路由配置的方法
相關文章
asp.net javascript 文件無刷新上傳實例代碼
最近在寫C# .net代碼的時候,遇到一個上傳刷新的問題。2009-06-06
ASP.NET?MVC5網(wǎng)站開發(fā)之用戶資料的修改和刪除3(七)
這篇文章主要為大家詳細介紹了ASP.NET?MVC5網(wǎng)站開發(fā)之用戶資料的修改和刪除,感興趣的小伙伴們可以參考一下2016-08-08
.Net中如何將一個實例的內存二進制內容讀出來(超簡單方法)
這篇文章主要介紹了如何將一個實例的內存二進制內容讀出來(超簡單方法),接下來的內容中,我們將利用一個簡單的方法輸出指定實例的字節(jié)序列,并此次分析值類型和引用類型實例在內存的布局,需要的朋友可以參考下2023-07-07
asp.net實現(xiàn)取消頁面表單內文本輸入框Enter響應的方法
這篇文章主要介紹了asp.net實現(xiàn)取消頁面表單內文本輸入框Enter響應的方法,結合實例形式分析了asp.net文本框Enter響應的原理與取消Enter響應的相關實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
詳解免費開源的DotNet二維碼操作組件ThoughtWorks.QRCode(.NET組件介紹之四)
本篇文章主要介紹了免費開源的DotNet二維碼操作組件ThoughtWorks.QRCode,非常具有實用價值,有興趣的同學可以來了解一下。2016-12-12
Linux上使用Docker部署ASP.NET?Core應用程序
這篇文章介紹了使用Docker部署ASP.NET?Core應用程序的方法,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-03-03

