.NET Core结合Nacos实现配置加解密的方法

 更新时间:2021年6月15日 15:00  点击:2084

背景

当我们把应用的配置都放到配置中心后,很多人会想到这样一个问题,配置里面有敏感的信息要怎么处理呢?

信息既然敏感的话,那么加个密就好了嘛,相信大部分人的第一感觉都是这个,确实这个是最简单也是最合适的方法。

其实很多人都在关注这个问题,好比说,数据库的连接字符串,调用第三方的密钥等等这些信息,都是不太想让很多人知道的。

那么如果我们把配置放在 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 里面拿一些额外的信息。

GetOrderGetFilterName 属于辅助信息,指定这个 ConfigFilter 的执行顺序(越小越先执行)和名称。

DoFilter 就是核心了,它可以变更 request 和 response ,这两个对象内部都会维护一个包含配置信息的 Dictionary。

换言之,只要我们定义一个 ConfigFilter,实现了这个接口,那么配置想怎么操作都可以了,加解密就是小问题了。

其中 NacosSdkOptions 里面加了两个配置项,是专门给这个功能用的 ConfigFilterAssembliesConfigFilterExtInfo

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实现配置加解密的方法的文章就介绍到这了,更多相关.NET Core Nacos配置加解密内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • IntelliJ IDEA2021.1 配置大全(超详细教程)

    这篇文章主要介绍了IntelliJ IDEA2021.1 配置大全(超详细教程),需要的朋友可以参考下...2021-04-18
  • Windows VPN服务器配置图文教程 超详细版

    VPN可以虚拟出一个专用网络,让远处的计算机和你相当于处在同一个局域网中,而中间的数据也可以实现加密传输,用处很大,特别是在一些大公司,分公司处在不同的区域。...2016-01-27
  • ASP.NET购物车实现过程详解

    这篇文章主要为大家详细介绍了ASP.NET购物车的实现过程,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-22
  • Tomcat配置及如何在Eclipse中启动

    这篇文章主要介绍了Tomcat配置及如何在Eclipse中启动,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-02-04
  • Laravel4安装配置的方法

    如果我们需要安培Laravel4的话最php最低要求要在php5.3.7版本并且我们需要把mcrypt与openss这两个扩展开启才可以,具体步骤我们参考下文。 前面我们介绍我了 com...2016-11-25
  • .NET Core下使用Kafka的方法步骤

    这篇文章主要介绍了.NET Core下使用Kafka的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • 在ASP.NET 2.0中操作数据之七十二:调试存储过程

    在开发过程中,使用Visual Studio的断点调试功能可以很方便帮我们调试发现程序存在的错误,同样Visual Studio也支持对SQL Server里面的存储过程进行调试,下面就让我们看看具体的调试方法。...2021-09-22
  • Win10 IIS 安装.net 4.5的方法

    这篇文章主要介绍了Win10 IIS 安装及.net 4.5及Win10安装IIS并配置ASP.NET 4.0的方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-22
  • 详解Maven profile配置管理及激活profile的几种方式

    这篇文章主要介绍了详解Maven profile配置管理及激活profile的几种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-26
  • IDEA如何添加配置文件到classpath中

    这篇文章主要介绍了IDEA如何添加配置文件到classpath中,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-09-19
  • 详解.NET Core 3.0 里新的JSON API

    这篇文章主要介绍了详解.NET Core 3.0 里新的JSON API,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • 查找php配置文件php.ini所在路径的二种方法

    通常php.ini的位置在:复制代码 代码如下:/etc目录下或/usr/local/lib目录下。如果你还是找不到php.ini或者找到了php.ini修改后不生效(其实是没找对),请使用如下办法:1.新建php文件,写入如下代码复制代码 代码如下:<?phpe...2014-05-31
  • 部署PHP时的4个配置修改说明

    以下就是部署PHP时的4个配置修改说明,大家一个一个进行学习研究。1、short_open_tag 是什么呢? 决定是否允许使用代码开始标志的缩写形式(<&#63; &#63;> )。如果要和 XML 结合使用PHP,可以禁用此选项以便于嵌入使用<&#63;x...2015-10-21
  • ASP.NET Core根据环境变量支持多个 appsettings.json配置文件

    这篇文章主要介绍了ASP.NET Core根据环境变量支持多个 appsettings.json配置文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • .net数据库操作框架SqlSugar的简单入门

    这篇文章主要介绍了.net数据库操作框架SqlSugar的简单入门,帮助大家更好的理解和学习使用.net技术,感兴趣的朋友可以了解下...2021-09-22
  • 记一次EFCore类型转换错误及解决方案

    这篇文章主要介绍了记一次EFCore类型转换错误及解决方案,帮助大家更好的理解和学习使用asp.net core,感兴趣的朋友可以了解下...2021-09-22
  • Vue-Router的routes配置详解

    在使用vue-router的项目中,实例化VueRouter是其配置选项routes该选项指定路由与视图的组件的关系或者路由与其他路由的关系,Router配置选项中是其中最重要的配置。本文就详细的介绍一下...2021-10-25
  • 华为畅享20Pro配置怎么样?华为畅享20Pro参数配置分析

    华为畅享20Pro配置怎么样?对于即将上市的华为畅享20 Pro手机,很多的网友们也是相当关注的,大家都想要知道这款华为畅享20 Pro手机的配置到底怎么样,赶紧看看吧...2020-06-29
  • 详解element-ui 表单校验 Rules 配置 常用黑科技

    这篇文章主要介绍了element-ui 表单校验 Rules 配置 常用黑科技,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-11
  • tomcat9 下载安装和配置+整合到eclipse的教程详解

    这篇文章主要介绍了tomcat9 下载安装和配置+整合到eclipse,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-28