如何在关系数据库(如Postgres)存储RsaSecurityKey用于IdentityServer4签名JWT
Got it, let's walk through this step by step—creating an RsaSecurityKey, storing it in PostgreSQL, and using it with IdentityServer4 to sign JWT tokens is totally doable with a few clear steps. Here's how to pull it off:
First, you can't serialize the RsaSecurityKey object directly to a database—you need to pull out its underlying RSA parameters. Use the built-in RSA class to generate a secure key pair:
using System.Security.Cryptography; using System.IdentityModel.Tokens.Jwt; // Generate a 2048-bit RSA key pair (4096-bit is better for high-security scenarios) using var rsa = RSA.Create(2048); // Export full key parameters (including private key components) var rsaParams = rsa.ExportParameters(includePrivateParameters: true); // These are the values you'll store in PostgreSQL: // rsaParams.Modulus, Exponent (public key parts) // rsaParams.D, P, Q, DP, DQ, InverseQ (private key parts)
Create a table to hold your signing keys—use bytea for binary parameter fields, and add metadata to track active keys and rotation:
CREATE TABLE signing_keys ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), key_id VARCHAR(255) UNIQUE NOT NULL, -- Used as the "kid" in JWT headers modulus BYTEA NOT NULL, exponent BYTEA NOT NULL, d BYTEA NOT NULL, -- Private key component p BYTEA NOT NULL, -- Private key component q BYTEA NOT NULL, -- Private key component dp BYTEA NOT NULL, -- Private key component dq BYTEA NOT NULL, -- Private key component inverse_q BYTEA NOT NULL, -- Private key component created_at TIMESTAMPTZ DEFAULT NOW(), is_active BOOLEAN DEFAULT true ); -- Optional: Index for fast active key lookup CREATE INDEX idx_signing_keys_active ON signing_keys(is_active);
If you're using Entity Framework Core, here's the matching entity class:
public class SigningKey { public Guid Id { get; set; } = Guid.NewGuid(); public string KeyId { get; set; } = Guid.NewGuid().ToString(); // Unique "kid" identifier public byte[] Modulus { get; set; } public byte[] Exponent { get; set; } public byte[] D { get; set; } public byte[] P { get; set; } public byte[] Q { get; set; } public byte[] DP { get; set; } public byte[] DQ { get; set; } public byte[] InverseQ { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public bool IsActive { get; set; } = true; }
Insert your generated key parameters into the database:
// Assuming you have a DbContext with a DbSet<SigningKey> var newSigningKey = new SigningKey { KeyId = "prod-signing-key-2024-v1", // Use a meaningful, unique ID Modulus = rsaParams.Modulus, Exponent = rsaParams.Exponent, D = rsaParams.D, P = rsaParams.P, Q = rsaParams.Q, DP = rsaParams.DP, DQ = rsaParams.DQ, InverseQ = rsaParams.InverseQ }; await dbContext.SigningKeys.AddAsync(newSigningKey); await dbContext.SaveChangesAsync();
When IdentityServer needs to sign tokens, fetch the active key from the database and rebuild the RsaSecurityKey:
private async Task<RsaSecurityKey> GetActiveSigningKeyAsync(AppDbContext dbContext) { // Fetch the currently active signing key var storedKey = await dbContext.SigningKeys .Where(k => k.IsActive) .FirstOrDefaultAsync(); if (storedKey == null) throw new InvalidOperationException("No active signing key found in the database"); // Reconstruct RSA parameters from stored values var rsaParams = new RSAParameters { Modulus = storedKey.Modulus, Exponent = storedKey.Exponent, D = storedKey.D, P = storedKey.P, Q = storedKey.Q, DP = storedKey.DP, DQ = storedKey.DQ, InverseQ = storedKey.InverseQ }; // Create RSA instance and import parameters using var rsa = RSA.Create(); rsa.ImportParameters(rsaParams); // Build RsaSecurityKey with matching KeyId (for JWT header "kid") return new RsaSecurityKey(rsa) { KeyId = storedKey.KeyId }; }
Update your IdentityServer4 setup to use this database-backed key instead of the default development key. In Program.cs:
builder.Services.AddIdentityServer() .AddInMemoryClients(Config.Clients) // Replace with your client config .AddInMemoryApiScopes(Config.ApiScopes) // Replace with your API scopes .AddInMemoryIdentityResources(Config.IdentityResources) // Replace with your identity resources .AddAspNetIdentity<ApplicationUser>() // If using ASP.NET Identity .AddSigningCredential(await GetActiveSigningKeyAsync(builder.Services.BuildServiceProvider().GetRequiredService<AppDbContext>()));
For better performance (to avoid repeated database calls), register the signing key as a singleton:
builder.Services.AddSingleton<ISigningCredentialStore>(async sp => { var dbContext = sp.GetRequiredService<AppDbContext>(); var securityKey = await GetActiveSigningKeyAsync(dbContext); // Use RS256 (RSA-SHA256) for secure signing var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256); return new InMemorySigningCredentialsStore(credentials); });
- Encrypt Private Key Data: Never store private key parameters (D, P, Q, etc.) in plaintext. Use PostgreSQL's
pgcryptoextension to encrypt these fields at rest, or use a secret manager like Azure Key Vault/HashiCorp Vault instead of direct database storage. - Key Rotation: Implement key rotation by adding new keys to the database and marking them as active. Keep old keys to validate existing tokens until they expire.
- Cache the Key: In production, cache the active signing key to avoid hitting the database on every token request.
- Use Strong Algorithms: Stick to
RsaSha256orRsaSha512—avoid outdated algorithms like SHA1.
内容的提问来源于stack exchange,提问作者dudedev

