使用数据库连接初始化 Serilog,无需在 .NET 中硬编码连接字符串

问题描述 投票:0回答:1

我正在尝试开发 .net API 作为副项目,并且我想添加日志记录功能。通过在线搜索,我发现了“Serilog”包,现在我正在尝试实现它。但问题是它需要一个到数据库的连接字符串。据我所知,我必须对其进行硬编码或从“appsettings.json”文件中调用它。但我想要一种更安全的方法。

我想出的解决方案是编写一个“Secret Manager”接口,以便稍后我可以实现“Azure Vault”等安全解决方案,但现在它只是解析 json 文件来获取机密。这是我的

ISecretManager
界面

public interface ISecretManager
{
    public string? GetPasswordManagerAuthDatabaseConnectionString();
    public string? GetLogDatabaseConnectionString();
    public byte[]? GetPepperSymmetricKey();
    public byte[]? GetHmacPrivateKey();
    public JwtInfo? GetJwtInfo();
    public byte[]? GetJwtKey();
    public MailInfo? GetMailInfo();
    public string GetRsaPrivateKey();
}

我在为数据库创建

dbContext
时遇到了类似的问题,但我能够在
dbContext
文件中解决此问题。

这是我的

dbContext
文件。

public class AuthDbContext : DbContext
{
        private readonly ISecretManager _secretManager;
        
        public AuthDbContext(DbContextOptions<AuthDbContext> options, ISecretManager secretManager) : base(options)
        {
            _secretManager = secretManager;
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (optionsBuilder.IsConfigured == false)
            {
                var connectionString = _secretManager.GetPasswordManagerAuthDatabaseConnectionString();
                if (connectionString == null)
                {
                    throw new SecretNotAvailableException();
                }
                
                optionsBuilder.UseNpgsql(connectionString);
            }
            base.OnConfiguring(optionsBuilder);
        }

        public DbSet<User> Users { get; set; }
        public DbSet<ActivationCode> ActivationCodes { get; set; }
        public DbSet<UserPassword> UserPasswords { get; set; }
}

我将我的秘密管理器注入到

dbContext
文件中并在那里处理配置。但是从在线教程来看 Serilog 是在构建器阶段创建的,我不能在这个阶段使用注入。

这是

Program.cs
文件的前半部分。

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
builder.Services.AddTransient<ISecretManager, FileBasedSecretManager>();
builder.Services.AddDbContext<AuthDbContext>();
builder.Services.AddTransient<IJwtService, JwtService>();
builder.Services.AddTransient<IUserService, UserService>();
builder.Services.AddTransient<IUserPasswordService, UserPasswordService>();
builder.Services.AddTransient<IUserRepository, UserRepository>();
builder.Services.AddTransient<IUserPasswordRepository, UserPasswordRepository>();
builder.Services.AddTransient<IActivationCodeRepository, ActivationCodeRepository>();
builder.Services.AddTransient<IActivationCodeService, ActivationCodeService>();
builder.Services.AddTransient<ICommunicationChannel, Mail>();
builder.Services.AddTransient<AuthenticationFacade>();

builder.Services.AddHttpContextAccessor();

builder.Host.UseSerilog(/* I need to pass connection string here */);

var app = builder.Build();

处理这个问题的最佳方法是什么?我可以直接创建一个秘密管理器对象,但这会违反“依赖倒置”原则,并且硬编码不是一种安全的方法。

c# .net serilog
1个回答
0
投票

我在这篇文章中解释了如何处理连接字符串而不是字符串,而是可以单独针对.Net配置系统覆盖的单独信息。

就我个人而言,我将实际密码放在来自 Kubernetes 机密的环境变量中,但您喜欢的任何其他 .Net 配置源都应该可以正常工作。

文章摘要

  1. 不要使用连接字符串:保存单个属性,如文章中所示(在此处复制,在此列表之后)。
  2. 创建映射到您的数据库选项的 POCO 类。
  3. 使用选项模式并将数据库选项注入您喜欢的服务中。本文建议创建一个连接工厂接口和相应的实现(取决于您实际的数据库服务器技术)。
  4. 本文展示了如何将工厂与
    Dapper
    Entity Framework
    一起使用。

选项示例:

{
  "SqlServer": {
    "Server": "my.sqlserver.enterprise.example.com",
    "Port": 1433,
    "Database": "my-db",
    "IntegratedSecurity": false,
    "Username": "sys_username",
    "Password": "undisclosed",
    "Mars": true,
    "ConnectionTimeout": "00:00:10"
  }
}

POCO 类使用

IOptions<>
(选项模式)将选项注入到任何服务:

public class SqlServerConnectionOptions
{
    #region Properties
    public string Server { get; set; }
    public int Port { get; set; }
    public string Database { get; set; }
    public bool IntegratedSecurity { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public bool Mars { get; set; }
    public TimeSpan? ConnectionTimeout { get; set; }
    public int? MaxPoolSize { get; set; }
    #endregion
}

使用选项模式的连接工厂的 MS SQL Server 实现:

using System.Data;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Options;

public class SqlServerConnectionFactory : IDbConnectionFactory
{
    #region Fields
    private Lazy<string> _connString;
    #endregion

    #region Constructors
    public SqlServerConnectionFactory(
        IOptions<SqlServerConnectionOptions> options
    )
    {
        _connString = new Lazy<string>(() =>
        {
            var opts = options.Value;
            // We use a special string builder.
            SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder();
            sb.DataSource = $"{opts.Server},{opts.Port}";
            sb.InitialCatalog = opts.Database;
            sb.IntegratedSecurity = opts.IntegratedSecurity;
            if (!opts.IntegratedSecurity)
            {
                sb.UserID = opts.Username;
                sb.Password = opts.Password;
            }
            sb.MultipleActiveResultSets = opts.Mars;
            if (opts.ConnectionTimeout.HasValue)
            {
                sb.ConnectTimeout = (int)opts.ConnectionTimeout.Value.TotalSeconds;
            }
            if (opts.MaxPoolSize.HasValue)
            {
                sb.MaxPoolSize = opts.MaxPoolSize.Value;
            }
            // Return the resulting connection string.
            return sb.ToString();
        });
    }

    #endregion

    #region IDbConnectionFactory
    public async Task<IDbConnection> CreateAsync()
    {
        SqlConnection conn = new(_connString.Value);
        await conn.OpenAsync();
        return conn;
    }
    #endregion
}
© www.soinside.com 2019 - 2024. All rights reserved.