聊一聊.NET Core结合Nacos实现配置加解密
背景
当我们把应用的配置都放到配置中心后,很多人会想到这样一个问题,配置里面有敏感的信息要怎么处理呢?
信息既然敏感的话,那么加个密就好了嘛,相信大部分人的第一感觉都是这个,确实这个是最简单也是最合适的方法。
其实很多人都在关注这个问题,好比说,数据库的连接字符串,调用第三方的密钥等等这些信息,都是不太想让很多人知道的。
那么如果我们把配置放在 Nacos 了,我们可以怎么操作呢?
想了想不外乎这么几种:
- 全部服务端搞定,客户端只管取;
- 全部客户端搞定,服务端只管存;
- 客户端为主,服务端为辅,服务端存一些加解密需要的辅助信息即可。
有一个老哥已经在 issue 里面提出了相关的落地方案,也包含了部分实现。
https://github.com/alibaba/nacos/issues/5367
简要概述的话就是,开个口子,用户可以在客户端拓展任意加解密方式,同时服务端可以辅助这一操作。
不过看了 2.0.2 的代码,服务端这一块的“辅助”还未完成,不过对客户端来说,这一块其实问题已经不大了。
6月14号发布的 nacos-sdk-csharp 1.1.0 版本已经支持了这一功能
下面就用 .NET 5 和 Nacos 2.0.2 为例,来简单说明一下。
简单原理说明
sdk 里面在进行配置相关读写操作的时候,会有一个 DoFilter 的操作。这个操作就是我们的切入点。
既然要执行 Filter , 那么执行的 Filter 从那里来呢? 答案是 IConfigFilter 。
sdk 里面提供了 IConfigFilter 这个接口,但是不提供实现,具体实现交由用户自定义,毕竟 100 个人就有 100 种不一样的实现。
下面看看它的定义。
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 的执行顺序(越小越先执行)和名称。
DoFilter 就是核心了,它可以变更 request 和 response ,这两个对象内部都会维护一个包含配置信息的 Dictionary。
换言之,只要我们定义一个 ConfigFilter,实现了这个接口,那么配置想怎么操作都可以了,加解密就是小问题了。
其中 NacosSdkOptions 里面加了两个配置项,是专门给这个功能用的 ConfigFilterAssemblies 和 ConfigFilterExtInfo
ConfigFilterAssemblies 是自定义 ConfigFilter 所在的程序集的名字,这里是一个字符串列表类型的参数,sdk 会根据这个名字去找到对应的实现,然后初始化好。
ConfigFilterExtInfo 是实现 ConfigFilter 是需要用到的扩展信息,这里是一个字符串类型的参数,扩展信息复杂的可以考虑传入一个 JSON 字符串。
下面来看个具体的例子吧。
自定义 ConfigFilter
这个 Filter 实现的效果是把部分敏感配置项进行加密,敏感的配置项需要在配置文件中指定。
先是 Init 方法:
public void Init(NacosSdkOptions options)
{
// 从 Options 里面的拓展信息获取需要加密的 json path
// 这里只是示例,根据具体情况调整成自己合适的!!!!
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 的相关操作都只是预留操作,现阶段可以不用理会。
还有一个 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 已经完成了,下面就是真正的应用了。
简单应用
老样子,建一个 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 的加解密就完成了。
写在最后
敏感配置项的加解密还是很有必要的,配置中心负责存储,客户端负责加解密,这样的方式可以让用户更加灵活的选择自己想要的加解密方法。
本文的示例代码已经上传到 Github,仅供参考。
https://github.com/catcherwong-archive/2021/tree/main/NacosConfigWithEncryption
最后的最后,希望感兴趣的大佬可以一起参与到这个项目来。
nacos-sdk-csharp 的地址 :https://github.com/nacos-group/nacos-sdk-csharp
聊一聊.NET Core结合Nacos实现配置加解密的更多相关文章
- laravel配置加解密
基于安全考虑,我们php项目配置文件中密码应该是加密的,laravel中也提供了OpenSSL 的 AES-256-CBC 来进行加密 但是如果我们项目配置的是其他加密方式,且希望以最少的改动实现读取 ...
- ASP.NET Core使用Nacos作为配置中心的多环境问题
前言 双11那天离职后,这段时间都待在家里,看看书,写写代码,逛逛招聘网站 周一去Gworld面试的时候,有听到面试官说他们用到了配置中心Apollo,聊下来,听他的意思,大概是处理了多环境这个比较方 ...
- C# AES 加解密处理
引言 这是一个有关AES加解密的方法类 一.设置AES加解密密钥:下面列出自己分配的三类密钥 private const string UserKey = "roshan-2015-user ...
- 在.NET Core中用最原生的方式读取Nacos的配置
背景 之前老黄写过一篇<ASP.NET Core结合Nacos来完成配置管理和服务发现>简单介绍了如何让.NET Core程序接入Nacos,之前的SDK里面更多的是对Nacos的Open ...
- Spring Cloud Nacos实现动态配置加载的源码分析
理解了上述Environment的基本原理后,如何从远程服务器上加载配置到Spring的Environment中. NacosPropertySourceLocator 顺着前面的分析思路,我们很自然 ...
- Nacos系列:基于Nacos的配置中心
前言 在看正文之前,我想请你回顾一下自己待过的公司都是怎么管理配置的,我想应该会有以下几种方式: 1.硬编码 没有什么配置不配置的,直接写在代码里面,比如使用常量类 优势:对开发友好,开发清楚地知道代 ...
- ASP.NET Core结合Nacos来完成配置管理和服务发现
目录 前言 Nacos的简介 启动Nacos 配置管理 服务发现 写在最后 前言 今年4月份的时候,和平台组的同事一起调研了一下Nacos,也就在那个时候写了.net core版本的非官方版的SDK. ...
- nacos作为配置中心兼容xml配置文件
最近公司想要用配置中心,因为公司用的有传统的spring项目,有springboot项目,为了兼容都能够采用配置中心,做了一些尝试,经过比较还是倾向于使用nacos,传统dubbo采用spring方式 ...
- .NET Core采用的全新配置系统[10]: 配置的同步机制是如何实现的?
配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.要了解配置同步机制的实现原理,先得从认识一个 ...
随机推荐
- 神经网络与机器学习 笔记—LMS(最小均方算法)和学习率退火
神经网络与机器学习 笔记-LMS(最小均方算法)和学习率退火 LMS算法和Rosenblatt感知器算法非常想,唯独就是去掉了神经元的压制函数,Rosenblatt用的Sgn压制函数,LMS不需要压制 ...
- 删除自定义服务 寒江孤钓<<windows 内核安全编程>> 学习笔记
书中第一章 成功启动first服务之后, 发现书中并没有介绍删除服务的方式, SRVINSTW工具中的移除服务功能,也无法找到我们要删除的服务, 于是,搜素了下,发现解决方法如下: 在cmd中输入 & ...
- GIF图片裁剪出指定大小的GIF图片
前言 最近在博客后台上传图片的时候,突然发现上传gif图片的时候裁剪图片有问题.既没法裁剪gif指定区域的图片,又没法裁剪指定区域生成一个新的指定大小的gif图.本来想直接去找个裁剪的库直接放上去的, ...
- spring boot 与 Mybatis整合(*)
在pom.xml文件中加入数据库.spring-mybatis整合 <!-- spring boot 整合mybatis --> <dependency> <groupI ...
- ElasticSearch第三弹之存储原理
我们上文中介绍的ES内部索引的写处理流程是在ES的内存中执行的,而数据被分配到特定的主.副分片上之后,最终是存储到磁盘上的,这样在断电的时候就不会丢失数据.具体的存储路径可在配置文件 ../confi ...
- 逆向工程初步160个crackme-------2
有了第一个crackme的经验后,这个crackme用了半个小时就验证成功了.(思路和第一个crackme相似) 动态调试工具:ollydbg (2.10) 文件分析工具:PEID (0.95) 同样 ...
- .NET Worker Service 如何优雅退出
上一篇文章中我们了解了 .NET Worker Service 的入门知识[1],今天我们接着介绍一下如何优雅地关闭和退出 Worker Service. Worker 类 从上一篇文章中,我们已经知 ...
- QFNU 10-02 19 training
B - Yet Another Crosses Problem 题意:找如果使图中某一行某一列全部变成黑色,至少需要把多少个白方格变成黑方格 思路:直接找就可以,注意存储的时候要记得进行分开存储,存储 ...
- 使用find_if算法搜寻map的value
// // main.cpp // map_find // // Created by PKU on 14-9-8. // Copyright (c) 2014年 PKU. All rights re ...
- 【BUAA_2020_软工】个人作业
个人项目作业博客 1. 在文章开头给出教学班级和可克隆的 Github 项目地址(例子如下).(1') 项目 内容 北航2020软工 班级博客 作业要求 具体要求 项目GitHub地址 个人项目 教学 ...