forked from xiongyuxing/tiku-backend.net
feat: harden SaaS authentication and authorization
This commit is contained in:
@@ -9,14 +9,73 @@ public sealed class JwtOptions
|
||||
public string Audience { get; set; } = "tiku-api";
|
||||
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
[System.ComponentModel.DataAnnotations.MinLength(32)]
|
||||
public string SigningKey { get; set; } = string.Empty;
|
||||
public string KeyId { get; set; } = "development-ephemeral";
|
||||
|
||||
public string PrivateKeyPem { get; set; } = string.Empty;
|
||||
|
||||
public Dictionary<string, string> PublicKeys { get; set; } = new(StringComparer.Ordinal);
|
||||
|
||||
[System.ComponentModel.DataAnnotations.Range(1, 1440)]
|
||||
public int AccessTokenMinutes { get; set; } = 30;
|
||||
public int AccessTokenMinutes { get; set; } = 15;
|
||||
|
||||
[System.ComponentModel.DataAnnotations.Range(1, 365)]
|
||||
public int RefreshTokenDays { get; set; } = 30;
|
||||
|
||||
public bool ValidateSessions { get; set; } = true;
|
||||
public static bool BeValid(JwtOptions options, bool isProduction)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(options.Issuer) ||
|
||||
string.IsNullOrWhiteSpace(options.Audience) ||
|
||||
string.IsNullOrWhiteSpace(options.KeyId) ||
|
||||
options.AccessTokenMinutes != 15 ||
|
||||
(isProduction && string.Equals(
|
||||
options.KeyId,
|
||||
"development-ephemeral",
|
||||
StringComparison.Ordinal)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.PrivateKeyPem))
|
||||
{
|
||||
if (isProduction)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!IsValidRsaPem(options.PrivateKeyPem, requirePrivateKey: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return options.PublicKeys.All(pair =>
|
||||
!string.IsNullOrWhiteSpace(pair.Key) &&
|
||||
!string.Equals(pair.Key, options.KeyId, StringComparison.Ordinal) &&
|
||||
IsValidRsaPem(pair.Value, requirePrivateKey: false));
|
||||
}
|
||||
|
||||
private static bool IsValidRsaPem(string pem, bool requirePrivateKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var rsa = System.Security.Cryptography.RSA.Create();
|
||||
rsa.ImportFromPem(pem);
|
||||
if (rsa.KeySize < 2048)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (requirePrivateKey)
|
||||
{
|
||||
_ = rsa.ExportParameters(includePrivateParameters: true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception) when (
|
||||
exception is ArgumentException or
|
||||
System.Security.Cryptography.CryptographicException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user