🐛 Dozen of bugs fixes

This commit is contained in:
2025-06-29 16:35:01 +08:00
parent f8295c6a18
commit 217b434cc4
12 changed files with 253 additions and 206 deletions

View File

@@ -9,6 +9,7 @@ using Microsoft.Extensions.Options;
using System.Text.Json.Serialization;
using DysonNetwork.Sphere.Account;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
namespace DysonNetwork.Sphere.Auth.OidcProvider.Controllers;
@@ -110,7 +111,7 @@ public class OidcProviderController(
return Ok(userInfo);
}
[HttpGet(".well-known/openid-configuration")]
[HttpGet("/.well-known/openid-configuration")]
public IActionResult GetConfiguration()
{
var baseUrl = configuration["BaseUrl"];
@@ -119,10 +120,10 @@ public class OidcProviderController(
return Ok(new
{
issuer = issuer,
authorization_endpoint = $"{baseUrl}/connect/authorize",
authorization_endpoint = $"{baseUrl}/auth/authorize",
token_endpoint = $"{baseUrl}/auth/open/token",
userinfo_endpoint = $"{baseUrl}/auth/open/userinfo",
jwks_uri = $"{baseUrl}/.well-known/openid-configuration/jwks",
jwks_uri = $"{baseUrl}/.well-known/jwks",
scopes_supported = new[] { "openid", "profile", "email" },
response_types_supported = new[]
{ "code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token" },
@@ -139,11 +140,17 @@ public class OidcProviderController(
});
}
[HttpGet("jwks")]
[HttpGet("/.well-known/jwks")]
public IActionResult GetJwks()
{
var keyBytes = Encoding.UTF8.GetBytes(options.Value.SigningKey);
var keyId = Convert.ToBase64String(SHA256.HashData(keyBytes)[..8])
using var rsa = options.Value.GetRsaPublicKey();
if (rsa == null)
{
return BadRequest("Public key is not configured");
}
var parameters = rsa.ExportParameters(false);
var keyId = Convert.ToBase64String(SHA256.HashData(parameters.Modulus!)[..8])
.Replace("+", "-")
.Replace("/", "_")
.Replace("=", "");
@@ -154,11 +161,12 @@ public class OidcProviderController(
{
new
{
kty = "oct",
kty = "RSA",
use = "sig",
kid = keyId,
k = Convert.ToBase64String(keyBytes),
alg = "HS256"
n = Base64UrlEncoder.Encode(parameters.Modulus!),
e = Base64UrlEncoder.Encode(parameters.Exponent!),
alg = "RS256"
}
}
});

View File

@@ -1,13 +1,36 @@
using System;
using System.Security.Cryptography;
namespace DysonNetwork.Sphere.Auth.OidcProvider.Options;
public class OidcProviderOptions
{
public string IssuerUri { get; set; } = "https://your-issuer-uri.com";
public string SigningKey { get; set; } = "replace-with-a-secure-random-key";
public string? PublicKeyPath { get; set; }
public string? PrivateKeyPath { get; set; }
public TimeSpan AccessTokenLifetime { get; set; } = TimeSpan.FromHours(1);
public TimeSpan RefreshTokenLifetime { get; set; } = TimeSpan.FromDays(30);
public TimeSpan AuthorizationCodeLifetime { get; set; } = TimeSpan.FromMinutes(5);
public bool RequireHttpsMetadata { get; set; } = true;
}
public RSA? GetRsaPrivateKey()
{
if (string.IsNullOrEmpty(PrivateKeyPath) || !File.Exists(PrivateKeyPath))
return null;
var privateKey = File.ReadAllText(PrivateKeyPath);
var rsa = RSA.Create();
rsa.ImportFromPem(privateKey.AsSpan());
return rsa;
}
public RSA? GetRsaPublicKey()
{
if (string.IsNullOrEmpty(PublicKeyPath) || !File.Exists(PublicKeyPath))
return null;
var publicKey = File.ReadAllText(PublicKeyPath);
var rsa = RSA.Create();
rsa.ImportFromPem(publicKey.AsSpan());
return rsa;
}
}

View File

@@ -75,7 +75,7 @@ public class OidcProviderService(
// Generate access token
var accessToken = GenerateJwtToken(client, session, expiresAt, scopes);
var refreshToken = GenerateRefreshToken();
var refreshToken = GenerateRefreshToken(session);
// In a real implementation, you would store the token in the database
// For this example, we'll just return the token without storing it
@@ -92,35 +92,38 @@ public class OidcProviderService(
}
private string GenerateJwtToken(
CustomApp client,
Session session,
Instant expiresAt,
IEnumerable<string>? scopes = null
CustomApp client,
Session session,
Instant expiresAt,
IEnumerable<string>? scopes = null
)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_options.SigningKey);
var clock = SystemClock.Instance;
var now = clock.GetCurrentInstant();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity([
new Claim(JwtRegisteredClaimNames.Sub, session.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, clock.GetCurrentInstant().ToUnixTimeSeconds().ToString(),
ClaimValueTypes.Integer64),
new Claim("client_id", client.Id.ToString())
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, now.ToUnixTimeSeconds().ToString(),
ClaimValueTypes.Integer64),
new Claim("client_id", client.Id.ToString())
]),
Expires = expiresAt.ToDateTimeUtc(),
Issuer = _options.IssuerUri,
Audience = client.Id.ToString(),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature
)
Audience = client.Id.ToString()
};
// Add scopes as claims if provided, otherwise use client's default scopes
// Try to use RSA signing if keys are available, fall back to HMAC
var rsaPrivateKey = _options.GetRsaPrivateKey();
tokenDescriptor.SigningCredentials = new SigningCredentials(
new RsaSecurityKey(rsaPrivateKey),
SecurityAlgorithms.RsaSha256
);
// Add scopes as claims if provided
var effectiveScopes = scopes?.ToList() ?? client.AllowedScopes?.ToList() ?? new List<string>();
if (effectiveScopes.Any())
{
@@ -132,15 +135,40 @@ public class OidcProviderService(
return tokenHandler.WriteToken(token);
}
private static string GenerateRefreshToken()
public (bool isValid, JwtSecurityToken? token) ValidateToken(string token)
{
using var rng = RandomNumberGenerator.Create();
var bytes = new byte[32];
rng.GetBytes(bytes);
return Convert.ToBase64String(bytes)
.Replace("+", "-")
.Replace("/", "_")
.Replace("=", "");
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = _options.IssuerUri,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
// Try to use RSA validation if public key is available
var rsaPublicKey = _options.GetRsaPublicKey();
validationParameters.IssuerSigningKey = new RsaSecurityKey(rsaPublicKey);
validationParameters.ValidateIssuerSigningKey = true;
validationParameters.ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 };
tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
return (true, (JwtSecurityToken)validatedToken);
}
catch (Exception ex)
{
logger.LogError(ex, "Token validation failed");
return (false, null);
}
}
private static string GenerateRefreshToken(Session session)
{
return Convert.ToBase64String(Encoding.UTF8.GetBytes(session.Id.ToString()));
}
private static bool VerifyHashedSecret(string secret, string hashedSecret)
@@ -150,35 +178,6 @@ public class OidcProviderService(
return string.Equals(secret, hashedSecret, StringComparison.Ordinal);
}
public JwtSecurityToken? ValidateToken(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_options.SigningKey);
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _options.IssuerUri,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
}, out var validatedToken);
return (JwtSecurityToken)validatedToken;
}
catch (Exception ex)
{
logger.LogError(ex, "Token validation failed");
return null;
}
}
// Authorization codes are now managed through ICacheService
public async Task<string> GenerateAuthorizationCodeAsync(
Guid clientId,
Guid userId,