You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

ASP.NET Core基于PostgreSQL的动态数据库连接与授权系统开发问题

我之前做过类似的PostgreSQL+ASP.NET Core的授权系统,刚好能解决你遇到的动态连接问题,下面一步步给你拆解实现思路:

核心思路与实现步骤

1. 调整配置文件为连接字符串模板

首先,把你appsettings.json里固定的连接字符串改成模板,这样后续可以动态替换登录用户的账号密码:

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "DBInfo": {
    "Name": "coresample",
    "ConnectionStringTemplate": "User ID={0};Password={1};Host=localhost;Port=5432;Database=coresample"
  }
}

这里用{0}{1}作为用户名、密码的占位符,后续通过字符串格式化生成真实的连接字符串。

2. 登录时验证用户并存储凭证

用户输入账号密码后,我们需要先验证这个PostgreSQL用户的合法性(直接用该凭证尝试连接数据库),验证通过后把用户信息存入身份认证的Claims中,方便后续请求获取:

public class AuthController : ControllerBase
{
    private readonly IConfiguration _configuration;

    public AuthController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        var connTemplate = _configuration.GetSection("DBInfo")["ConnectionStringTemplate"];
        var testConnString = string.Format(connTemplate, request.Username, request.Password);

        // 验证PostgreSQL用户是否有效
        try
        {
            using var conn = new NpgsqlConnection(testConnString);
            await conn.OpenAsync();
            // 执行简单查询确认连接正常
            await conn.ExecuteScalarAsync("SELECT 1");
        }
        catch (NpgsqlException ex)
        {
            // 根据PostgreSQL错误码判断是密码错误还是用户不存在
            return BadRequest("用户名或密码错误");
        }

        // 生成Claims,这里建议对密码加密存储,避免明文泄露
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, request.Username),
            new Claim("EncryptedPassword", EncryptPassword(request.Password)) // 替换成你的加密逻辑
        };

        var identity = new ClaimsIdentity(claims, "PostgresAuth");
        await HttpContext.SignInAsync(new ClaimsPrincipal(identity));

        return Ok(new { Message = "登录成功" });
    }

    // 示例加密方法,实际用你项目的加密方案
    private string EncryptPassword(string password)
    {
        // 这里省略具体加密逻辑,比如用AES或RSA加密
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(password));
    }
}

public class LoginRequest
{
    public string Username { get; set; }
    public string Password { get; set; }
}

3. 实现动态数据库连接工厂

创建一个可注入的连接工厂服务,每次业务请求时从当前用户的Claims中获取凭证,动态生成连接字符串并创建数据库连接:

public interface IDbConnectionFactory
{
    Task<IDbConnection> CreateConnectionAsync();
}

public class PostgresDbConnectionFactory : IDbConnectionFactory
{
    private readonly IConfiguration _configuration;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public PostgresDbConnectionFactory(IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
    {
        _configuration = configuration;
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task<IDbConnection> CreateConnectionAsync()
    {
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext == null || !httpContext.User.Identity.IsAuthenticated)
        {
            throw new UnauthorizedAccessException("用户未登录,请先登录");
        }

        var username = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
        var encryptedPassword = httpContext.User.FindFirstValue("EncryptedPassword");
        if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(encryptedPassword))
        {
            throw new InvalidOperationException("用户身份信息不完整");
        }

        // 解密密码
        var password = DecryptPassword(encryptedPassword);
        var connTemplate = _configuration.GetSection("DBInfo")["ConnectionStringTemplate"];
        var connString = string.Format(connTemplate, username, password);

        var conn = new NpgsqlConnection(connString);
        await conn.OpenAsync();
        return conn;
    }

    // 示例解密方法
    private string DecryptPassword(string encryptedPassword)
    {
        return Encoding.UTF8.GetString(Convert.FromBase64String(encryptedPassword));
    }
}

然后在Program.cs(或Startup.cs)中注册服务:

// .NET 6+ Program.cs
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<IDbConnectionFactory, PostgresDbConnectionFactory>();

4. 业务层使用动态连接

在你的Repository或业务逻辑类中注入IDbConnectionFactory,用它创建连接执行Dapper操作,确保每个用户访问自己的专属表:

public class UserDataRepository
{
    private readonly IDbConnectionFactory _dbConnectionFactory;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public UserDataRepository(IDbConnectionFactory dbConnectionFactory, IHttpContextAccessor httpContextAccessor)
    {
        _dbConnectionFactory = dbConnectionFactory;
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task<IEnumerable<UserExclusiveData>> GetUserExclusiveDataAsync()
    {
        using var conn = await _dbConnectionFactory.CreateConnectionAsync();
        // 假设专属表名和用户名关联,比如data_{username}
        var username = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
        var tableName = $"data_{username}";
        return await conn.QueryAsync<UserExclusiveData>($"SELECT * FROM {tableName}");
    }
}

public class UserExclusiveData
{
    // 你的数据表字段对应属性
    public int Id { get; set; }
    public string Content { get; set; }
}
关键注意事项
  • 密码安全:绝对不要把明文密码存在Claims或Session中,一定要加密存储,比如用AES加密或者ASP.NET Core的DataProtection API。
  • 权限控制:确保每个PostgreSQL用户只有自己专属表的操作权限,比如执行GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE data_john TO john;给用户授权。
  • 连接池管理:PostgreSQL的连接池是基于连接字符串的,每个用户会占用独立的连接池,要注意服务器的最大连接数限制,避免连接耗尽。
  • 错误处理:在创建连接和执行操作时要捕获NpgsqlException,根据错误码区分权限不足、连接失败等场景,返回友好的错误信息。

内容的提问来源于stack exchange,提问作者Travis Bincle

火山引擎 最新活动