feat: 添加OAuth2认证配置和实现
添加OAuth2认证相关配置文件和服务实现,包括环境变量配置、PKCE流程支持、token管理等功能。主要变更: - 新增OAuth2配置文件 - 实现OAuth2服务层 - 更新请求拦截器支持token自动刷新 - 修改认证API和store以支持OAuth2流程
This commit is contained in:
parent
cf1bf1d600
commit
61c3a27192
@ -1,9 +1,6 @@
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Models.Dtos;
|
||||
using Fengling.Console.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Fengling.Console.Controllers;
|
||||
|
||||
@ -35,26 +32,9 @@ public class OAuthClientsController : ControllerBase
|
||||
{
|
||||
var (items, totalCount) = await _service.GetClientsAsync(page, pageSize, displayName, clientId, status);
|
||||
|
||||
var result = items.Select(c => new OAuthClientDto
|
||||
{
|
||||
Id = c.Id,
|
||||
ClientId = c.ClientId,
|
||||
DisplayName = c.DisplayName,
|
||||
RedirectUris = c.RedirectUris,
|
||||
PostLogoutRedirectUris = c.PostLogoutRedirectUris,
|
||||
Scopes = c.Scopes,
|
||||
GrantTypes = c.GrantTypes,
|
||||
ClientType = c.ClientType,
|
||||
ConsentType = c.ConsentType,
|
||||
Status = c.Status,
|
||||
Description = c.Description,
|
||||
CreatedAt = c.CreatedAt,
|
||||
UpdatedAt = c.UpdatedAt,
|
||||
});
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
items = result,
|
||||
items,
|
||||
totalCount,
|
||||
page,
|
||||
pageSize
|
||||
@ -74,7 +54,7 @@ public class OAuthClientsController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<OAuthClientDto>> GetClient(long id)
|
||||
public async Task<ActionResult<object>> GetClient(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -84,22 +64,7 @@ public class OAuthClientsController : ControllerBase
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(new OAuthClientDto
|
||||
{
|
||||
Id = client.Id,
|
||||
ClientId = client.ClientId,
|
||||
DisplayName = client.DisplayName,
|
||||
RedirectUris = client.RedirectUris,
|
||||
PostLogoutRedirectUris = client.PostLogoutRedirectUris,
|
||||
Scopes = client.Scopes,
|
||||
GrantTypes = client.GrantTypes,
|
||||
ClientType = client.ClientType,
|
||||
ConsentType = client.ConsentType,
|
||||
Status = client.Status,
|
||||
Description = client.Description,
|
||||
CreatedAt = client.CreatedAt,
|
||||
UpdatedAt = client.UpdatedAt,
|
||||
});
|
||||
return Ok(client);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -109,37 +74,12 @@ public class OAuthClientsController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<OAuthClientDto>> CreateClient([FromBody] CreateOAuthClientDto dto)
|
||||
public async Task<ActionResult<object>> CreateClient([FromBody] CreateClientDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = new OAuthApplication
|
||||
{
|
||||
ClientId = dto.ClientId,
|
||||
ClientSecret = string.IsNullOrEmpty(dto.ClientSecret) ? GenerateSecureSecret() : dto.ClientSecret,
|
||||
DisplayName = dto.DisplayName,
|
||||
RedirectUris = dto.RedirectUris ?? Array.Empty<string>(),
|
||||
PostLogoutRedirectUris = dto.PostLogoutRedirectUris ?? Array.Empty<string>(),
|
||||
Scopes = dto.Scopes ?? new[] { "openid", "profile", "email", "api" },
|
||||
GrantTypes = dto.GrantTypes ?? new[] { "authorization_code", "refresh_token" },
|
||||
ClientType = dto.ClientType ?? "confidential",
|
||||
ConsentType = dto.ConsentType ?? "explicit",
|
||||
Status = dto.Status ?? "active",
|
||||
Description = dto.Description,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
var created = await _service.CreateClientAsync(client);
|
||||
|
||||
return CreatedAtAction(nameof(GetClient), new { id = created.Id }, new OAuthClientDto
|
||||
{
|
||||
Id = created.Id,
|
||||
ClientId = created.ClientId,
|
||||
ClientSecret = created.ClientSecret,
|
||||
DisplayName = created.DisplayName,
|
||||
Status = created.Status,
|
||||
CreatedAt = created.CreatedAt,
|
||||
});
|
||||
var result = await _service.CreateClientAsync(dto);
|
||||
return StatusCode(201, result);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
@ -153,17 +93,12 @@ public class OAuthClientsController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost("{id}/generate-secret")]
|
||||
public async Task<ActionResult> GenerateSecret(long id)
|
||||
public async Task<ActionResult> GenerateSecret(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = await _service.GenerateSecretAsync(id);
|
||||
return Ok(new
|
||||
{
|
||||
clientId = client.ClientId,
|
||||
clientSecret = client.ClientSecret,
|
||||
message = "新密钥已生成,请妥善保管,刷新后将无法再次查看"
|
||||
});
|
||||
var result = await _service.GenerateSecretAsync(id);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
@ -176,74 +111,8 @@ public class OAuthClientsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{id}/toggle-status")]
|
||||
public async Task<ActionResult> ToggleStatus(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = await _service.ToggleStatusAsync(id);
|
||||
return Ok(new
|
||||
{
|
||||
clientId = client.ClientId,
|
||||
newStatus = client.Status,
|
||||
message = $"客户端状态已更改为 {client.Status}"
|
||||
});
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return NotFound(new { message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error toggling status for client {Id}", id);
|
||||
return StatusCode(500, new { message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> UpdateClient(long id, [FromBody] UpdateOAuthClientDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = await _service.GetClientAsync(id);
|
||||
if (client == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var updated = new OAuthApplication
|
||||
{
|
||||
Id = id,
|
||||
ClientId = client.ClientId,
|
||||
ClientSecret = client.ClientSecret,
|
||||
DisplayName = dto.DisplayName,
|
||||
RedirectUris = dto.RedirectUris,
|
||||
PostLogoutRedirectUris = dto.PostLogoutRedirectUris,
|
||||
Scopes = dto.Scopes,
|
||||
GrantTypes = dto.GrantTypes,
|
||||
ClientType = dto.ClientType,
|
||||
ConsentType = dto.ConsentType,
|
||||
Status = dto.Status,
|
||||
Description = dto.Description,
|
||||
CreatedAt = client.CreatedAt,
|
||||
};
|
||||
|
||||
await _service.UpdateClientAsync(id, updated);
|
||||
return NoContent();
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return NotFound(new { message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating client {Id}", id);
|
||||
return StatusCode(500, new { message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteClient(long id)
|
||||
public async Task<IActionResult> DeleteClient(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -261,10 +130,22 @@ public class OAuthClientsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
private static string GenerateSecureSecret(int length = 32)
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> UpdateClient(string id, [FromBody] UpdateClientDto dto)
|
||||
{
|
||||
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var bytes = System.Security.Cryptography.RandomNumberGenerator.GetBytes(length);
|
||||
return new string(bytes.Select(b => chars[b % chars.Length]).ToArray());
|
||||
try
|
||||
{
|
||||
await _service.UpdateClientAsync(id, dto);
|
||||
return NoContent();
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return NotFound(new { message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating client {Id}", id);
|
||||
return StatusCode(500, new { message = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
namespace Fengling.Console.Models.Dtos;
|
||||
|
||||
public record CreateOAuthClientDto
|
||||
{
|
||||
public string ClientId { get; init; } = string.Empty;
|
||||
public string? ClientSecret { get; init; }
|
||||
public string DisplayName { get; init; } = string.Empty;
|
||||
public string[]? RedirectUris { get; init; }
|
||||
public string[]? PostLogoutRedirectUris { get; init; }
|
||||
public string[]? Scopes { get; init; }
|
||||
public string[]? GrantTypes { get; init; }
|
||||
public string? ClientType { get; init; }
|
||||
public string? ConsentType { get; init; }
|
||||
public string? Status { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
public record UpdateOAuthClientDto
|
||||
{
|
||||
public string? DisplayName { get; init; }
|
||||
public string[]? RedirectUris { get; init; }
|
||||
public string[]? PostLogoutRedirectUris { get; init; }
|
||||
public string[]? Scopes { get; init; }
|
||||
public string[]? GrantTypes { get; init; }
|
||||
public string? ClientType { get; init; }
|
||||
public string? ConsentType { get; init; }
|
||||
public string? Status { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
public record OAuthClientDto
|
||||
{
|
||||
public long Id { get; init; }
|
||||
public string ClientId { get; init; } = string.Empty;
|
||||
public string? ClientSecret { get; init; }
|
||||
public string DisplayName { get; init; } = string.Empty;
|
||||
public string[] RedirectUris { get; init; } = Array.Empty<string>();
|
||||
public string[] PostLogoutRedirectUris { get; init; } = Array.Empty<string>();
|
||||
public string[] Scopes { get; init; } = Array.Empty<string>();
|
||||
public string[] GrantTypes { get; init; } = Array.Empty<string>();
|
||||
public string ClientType { get; init; } = "public";
|
||||
public string ConsentType { get; init; } = "implicit";
|
||||
public string Status { get; init; } = "active";
|
||||
public string? Description { get; init; }
|
||||
public DateTime CreatedAt { get; init; }
|
||||
public DateTime? UpdatedAt { get; init; }
|
||||
}
|
||||
@ -25,7 +25,7 @@ builder.Services.AddIdentity<ApplicationUser, ApplicationRole>()
|
||||
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
|
||||
builder.Services.AddScoped<IOAuthClientRepository, OAuthClientRepository>();
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddScoped<IOAuthClientService, OAuthClientService>();
|
||||
builder.Services.AddScoped<IUserRepository, UserRepository>();
|
||||
builder.Services.AddScoped<ITenantRepository, TenantRepository>();
|
||||
|
||||
@ -1,107 +0,0 @@
|
||||
using Fengling.AuthService.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Fengling.Console.Repositories;
|
||||
|
||||
public interface IOAuthClientRepository
|
||||
{
|
||||
Task<OAuthApplication?> GetByIdAsync(long id);
|
||||
Task<OAuthApplication?> GetByClientIdAsync(string clientId);
|
||||
Task<IEnumerable<OAuthApplication>> GetAllAsync();
|
||||
Task<IEnumerable<OAuthApplication>> GetPagedAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null);
|
||||
Task<int> CountAsync(string? displayName = null, string? clientId = null, string? status = null);
|
||||
Task AddAsync(OAuthApplication client);
|
||||
Task UpdateAsync(OAuthApplication client);
|
||||
Task DeleteAsync(OAuthApplication client);
|
||||
}
|
||||
|
||||
public class OAuthClientRepository : IOAuthClientRepository
|
||||
{
|
||||
private readonly Fengling.AuthService.Data.ApplicationDbContext _context;
|
||||
|
||||
public OAuthClientRepository(Fengling.AuthService.Data.ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<OAuthApplication?> GetByIdAsync(long id)
|
||||
{
|
||||
return await _context.OAuthApplications.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<OAuthApplication?> GetByClientIdAsync(string clientId)
|
||||
{
|
||||
return await _context.OAuthApplications.FirstOrDefaultAsync(c => c.ClientId == clientId);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OAuthApplication>> GetAllAsync()
|
||||
{
|
||||
return await _context.OAuthApplications.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OAuthApplication>> GetPagedAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null)
|
||||
{
|
||||
var query = _context.OAuthApplications.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(displayName))
|
||||
{
|
||||
query = query.Where(c => c.DisplayName.Contains(displayName));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(clientId))
|
||||
{
|
||||
query = query.Where(c => c.ClientId.Contains(clientId));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(status))
|
||||
{
|
||||
query = query.Where(c => c.Status == status);
|
||||
}
|
||||
|
||||
return await query
|
||||
.OrderByDescending(c => c.CreatedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync(string? displayName = null, string? clientId = null, string? status = null)
|
||||
{
|
||||
var query = _context.OAuthApplications.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(displayName))
|
||||
{
|
||||
query = query.Where(c => c.DisplayName.Contains(displayName));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(clientId))
|
||||
{
|
||||
query = query.Where(c => c.ClientId.Contains(clientId));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(status))
|
||||
{
|
||||
query = query.Where(c => c.Status == status);
|
||||
}
|
||||
|
||||
return await query.CountAsync();
|
||||
}
|
||||
|
||||
public async Task AddAsync(OAuthApplication client)
|
||||
{
|
||||
_context.OAuthApplications.Add(client);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(OAuthApplication client)
|
||||
{
|
||||
_context.OAuthApplications.Update(client);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(OAuthApplication client)
|
||||
{
|
||||
_context.OAuthApplications.Remove(client);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@ -1,248 +1,260 @@
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Repositories;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Fengling.Console.Services;
|
||||
|
||||
public interface IOAuthClientService
|
||||
{
|
||||
Task<(IEnumerable<OAuthApplication> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null);
|
||||
Task<OAuthApplication?> GetClientAsync(long id);
|
||||
Task<OAuthApplication> CreateClientAsync(OAuthApplication client);
|
||||
Task<OAuthApplication> GenerateSecretAsync(long id);
|
||||
Task<OAuthApplication> ToggleStatusAsync(long id);
|
||||
Task<OAuthApplication> UpdateClientAsync(long id, OAuthApplication updatedClient);
|
||||
Task DeleteClientAsync(long id);
|
||||
Task<(IEnumerable<object> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null);
|
||||
Task<object?> GetClientAsync(string id);
|
||||
Task<object> CreateClientAsync(CreateClientDto dto);
|
||||
Task<object> GenerateSecretAsync(string id);
|
||||
Task<object> UpdateClientAsync(string id, UpdateClientDto dto);
|
||||
Task DeleteClientAsync(string id);
|
||||
object GetClientOptions();
|
||||
}
|
||||
|
||||
public record CreateClientDto
|
||||
{
|
||||
public string ClientId { get; init; } = string.Empty;
|
||||
public string? ClientSecret { get; init; }
|
||||
public string DisplayName { get; init; } = string.Empty;
|
||||
public string[]? RedirectUris { get; init; }
|
||||
public string[]? PostLogoutRedirectUris { get; init; }
|
||||
public string[]? Scopes { get; init; }
|
||||
public string[]? GrantTypes { get; init; }
|
||||
public string? ClientType { get; init; }
|
||||
public string? ConsentType { get; init; }
|
||||
public string? Status { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
public record UpdateClientDto
|
||||
{
|
||||
public string? DisplayName { get; init; }
|
||||
public string[]? RedirectUris { get; init; }
|
||||
public string[]? PostLogoutRedirectUris { get; init; }
|
||||
public string[]? Scopes { get; init; }
|
||||
public string[]? GrantTypes { get; init; }
|
||||
public string? ClientType { get; init; }
|
||||
public string? ConsentType { get; init; }
|
||||
public string? Status { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
public class OAuthClientService : IOAuthClientService
|
||||
{
|
||||
private readonly IOAuthClientRepository _repository;
|
||||
private readonly IOpenIddictApplicationManager _applicationManager;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<OAuthClientService> _logger;
|
||||
|
||||
public OAuthClientService(
|
||||
IOAuthClientRepository repository,
|
||||
IOpenIddictApplicationManager applicationManager,
|
||||
HttpClient httpClient,
|
||||
IConfiguration configuration,
|
||||
ILogger<OAuthClientService> logger)
|
||||
{
|
||||
_repository = repository;
|
||||
_applicationManager = applicationManager;
|
||||
_httpClient = httpClient;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<OAuthApplication> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null)
|
||||
public async Task<(IEnumerable<object> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null)
|
||||
{
|
||||
var items = await _repository.GetPagedAsync(page, pageSize, displayName, clientId, status);
|
||||
var totalCount = await _repository.CountAsync(displayName, clientId, status);
|
||||
return (items, totalCount);
|
||||
var applications = _applicationManager.ListAsync();
|
||||
var clientList = new List<object>();
|
||||
|
||||
await foreach (var application in applications)
|
||||
{
|
||||
var clientIdValue = await _applicationManager.GetClientIdAsync(application);
|
||||
var displayNameValue = await _applicationManager.GetDisplayNameAsync(application);
|
||||
|
||||
if (!string.IsNullOrEmpty(displayName) && !displayNameValue.Contains(displayName, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(clientId) && !clientIdValue.Contains(clientId, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var clientType = await _applicationManager.GetClientTypeAsync(application);
|
||||
var consentType = await _applicationManager.GetConsentTypeAsync(application);
|
||||
var permissions = await _applicationManager.GetPermissionsAsync(application);
|
||||
var redirectUris = await _applicationManager.GetRedirectUrisAsync(application);
|
||||
var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application);
|
||||
|
||||
clientList.Add(new
|
||||
{
|
||||
id = application,
|
||||
clientId = clientIdValue,
|
||||
displayName = displayNameValue,
|
||||
redirectUris = redirectUris.ToArray(),
|
||||
postLogoutRedirectUris = postLogoutRedirectUris.ToArray(),
|
||||
scopes = permissions
|
||||
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length)),
|
||||
grantTypes = permissions
|
||||
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)),
|
||||
clientType = clientType?.ToString(),
|
||||
consentType = consentType?.ToString(),
|
||||
status = "active"
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<OAuthApplication?> GetClientAsync(long id)
|
||||
{
|
||||
return await _repository.GetByIdAsync(id);
|
||||
var sortedClients = clientList
|
||||
.OrderByDescending(c => (c as dynamic).clientId)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
|
||||
return (sortedClients, clientList.Count);
|
||||
}
|
||||
|
||||
public async Task<OAuthApplication> CreateClientAsync(OAuthApplication client)
|
||||
public async Task<object?> GetClientAsync(string id)
|
||||
{
|
||||
var existing = await _repository.GetByClientIdAsync(client.ClientId);
|
||||
if (existing != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Client ID '{client.ClientId}' 已存在");
|
||||
}
|
||||
|
||||
var descriptor = new OpenIddictApplicationDescriptor
|
||||
{
|
||||
ClientId = client.ClientId,
|
||||
ClientSecret = client.ClientSecret,
|
||||
DisplayName = client.DisplayName
|
||||
};
|
||||
|
||||
foreach (var uri in client.RedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
||||
{
|
||||
descriptor.RedirectUris.Add(new Uri(uri));
|
||||
}
|
||||
|
||||
foreach (var uri in client.PostLogoutRedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
||||
{
|
||||
descriptor.PostLogoutRedirectUris.Add(new Uri(uri));
|
||||
}
|
||||
|
||||
foreach (var grantType in client.GrantTypes)
|
||||
{
|
||||
descriptor.Permissions.Add(grantType);
|
||||
}
|
||||
|
||||
foreach (var scope in client.Scopes)
|
||||
{
|
||||
descriptor.Permissions.Add(scope);
|
||||
}
|
||||
|
||||
await _applicationManager.CreateAsync(descriptor);
|
||||
await _repository.AddAsync(client);
|
||||
|
||||
_logger.LogInformation("Created OAuth client {ClientId}", client.ClientId);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public async Task<OAuthApplication> GenerateSecretAsync(long id)
|
||||
{
|
||||
var client = await _repository.GetByIdAsync(id);
|
||||
if (client == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Client with ID {id} not found");
|
||||
}
|
||||
|
||||
var application = await _applicationManager.FindByClientIdAsync(client.ClientId);
|
||||
var application = await _applicationManager.FindByIdAsync(id);
|
||||
if (application == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"OpenIddict application for client {client.ClientId} not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
var newSecret = GenerateSecureSecret();
|
||||
var clientIdValue = await _applicationManager.GetClientIdAsync(application);
|
||||
var displayNameValue = await _applicationManager.GetDisplayNameAsync(application);
|
||||
var clientType = await _applicationManager.GetClientTypeAsync(application);
|
||||
var consentType = await _applicationManager.GetConsentTypeAsync(application);
|
||||
var permissions = await _applicationManager.GetPermissionsAsync(application);
|
||||
var redirectUris = await _applicationManager.GetRedirectUrisAsync(application);
|
||||
var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application);
|
||||
|
||||
var descriptor = new OpenIddictApplicationDescriptor
|
||||
return new
|
||||
{
|
||||
ClientId = client.ClientId,
|
||||
ClientSecret = newSecret,
|
||||
DisplayName = client.DisplayName
|
||||
id = id,
|
||||
clientId = clientIdValue,
|
||||
displayName = displayNameValue,
|
||||
redirectUris = redirectUris.ToArray(),
|
||||
postLogoutRedirectUris = postLogoutRedirectUris.ToArray(),
|
||||
scopes = permissions
|
||||
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length)),
|
||||
grantTypes = permissions
|
||||
.Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
|
||||
.Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)),
|
||||
clientType = clientType?.ToString(),
|
||||
consentType = consentType?.ToString(),
|
||||
status = "active"
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<object> CreateClientAsync(CreateClientDto dto)
|
||||
{
|
||||
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
|
||||
var token = await GetAuthTokenAsync();
|
||||
|
||||
var clientSecret = string.IsNullOrEmpty(dto.ClientSecret) ? GenerateSecureSecret() : dto.ClientSecret;
|
||||
|
||||
var requestBody = new
|
||||
{
|
||||
dto.ClientId,
|
||||
ClientSecret = clientSecret,
|
||||
dto.DisplayName,
|
||||
dto.RedirectUris,
|
||||
dto.PostLogoutRedirectUris,
|
||||
dto.Scopes,
|
||||
dto.GrantTypes,
|
||||
dto.ClientType,
|
||||
dto.ConsentType,
|
||||
dto.Status,
|
||||
dto.Description
|
||||
};
|
||||
|
||||
foreach (var uri in client.RedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/api/OAuthClients")
|
||||
{
|
||||
descriptor.RedirectUris.Add(new Uri(uri));
|
||||
Content = new StringContent(
|
||||
System.Text.Json.JsonSerializer.Serialize(requestBody),
|
||||
System.Text.Encoding.UTF8,
|
||||
"application/json")
|
||||
};
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<object>(content);
|
||||
|
||||
_logger.LogInformation("Created OAuth client {ClientId}", dto.ClientId);
|
||||
|
||||
return result!;
|
||||
}
|
||||
|
||||
foreach (var uri in client.PostLogoutRedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
||||
public async Task<object> GenerateSecretAsync(string id)
|
||||
{
|
||||
descriptor.PostLogoutRedirectUris.Add(new Uri(uri));
|
||||
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
|
||||
var token = await GetAuthTokenAsync();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/api/OAuthClients/{id}/generate-secret");
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<object>(content);
|
||||
|
||||
_logger.LogInformation("Generated new secret for OAuth client {Id}", id);
|
||||
|
||||
return result!;
|
||||
}
|
||||
|
||||
foreach (var grantType in client.GrantTypes)
|
||||
public async Task<object> UpdateClientAsync(string id, UpdateClientDto dto)
|
||||
{
|
||||
descriptor.Permissions.Add(grantType);
|
||||
}
|
||||
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
|
||||
var token = await GetAuthTokenAsync();
|
||||
|
||||
foreach (var scope in client.Scopes)
|
||||
var requestBody = new
|
||||
{
|
||||
descriptor.Permissions.Add(scope);
|
||||
}
|
||||
|
||||
await _applicationManager.UpdateAsync(application, descriptor);
|
||||
|
||||
client.ClientSecret = newSecret;
|
||||
client.UpdatedAt = DateTime.UtcNow;
|
||||
await _repository.UpdateAsync(client);
|
||||
|
||||
_logger.LogInformation("Generated new secret for OAuth client {ClientId}", client.ClientId);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public async Task<OAuthApplication> ToggleStatusAsync(long id)
|
||||
{
|
||||
var client = await _repository.GetByIdAsync(id);
|
||||
if (client == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Client with ID {id} not found");
|
||||
}
|
||||
|
||||
var oldStatus = client.Status;
|
||||
client.Status = client.Status == "active" ? "inactive" : "active";
|
||||
client.UpdatedAt = DateTime.UtcNow;
|
||||
await _repository.UpdateAsync(client);
|
||||
|
||||
_logger.LogInformation("Toggled OAuth client {ClientId} status from {OldStatus} to {NewStatus}", client.ClientId, oldStatus, client.Status);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public async Task<OAuthApplication> UpdateClientAsync(long id, OAuthApplication updatedClient)
|
||||
{
|
||||
var client = await _repository.GetByIdAsync(id);
|
||||
if (client == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Client with ID {id} not found");
|
||||
}
|
||||
|
||||
var application = await _applicationManager.FindByClientIdAsync(client.ClientId);
|
||||
if (application == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"OpenIddict application for client {client.ClientId} not found");
|
||||
}
|
||||
|
||||
var descriptor = new OpenIddictApplicationDescriptor
|
||||
{
|
||||
ClientId = client.ClientId,
|
||||
ClientSecret = client.ClientSecret,
|
||||
DisplayName = updatedClient.DisplayName ?? client.DisplayName
|
||||
dto.DisplayName,
|
||||
dto.RedirectUris,
|
||||
dto.PostLogoutRedirectUris,
|
||||
dto.Scopes,
|
||||
dto.GrantTypes,
|
||||
dto.ClientType,
|
||||
dto.ConsentType,
|
||||
dto.Status,
|
||||
dto.Description
|
||||
};
|
||||
|
||||
var redirectUris = updatedClient.RedirectUris ?? client.RedirectUris;
|
||||
foreach (var uri in redirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
||||
var request = new HttpRequestMessage(HttpMethod.Put, $"{authServiceUrl}/api/OAuthClients/{id}")
|
||||
{
|
||||
descriptor.RedirectUris.Add(new Uri(uri));
|
||||
Content = new StringContent(
|
||||
System.Text.Json.JsonSerializer.Serialize(requestBody),
|
||||
System.Text.Encoding.UTF8,
|
||||
"application/json")
|
||||
};
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
_logger.LogInformation("Updated OAuth client {Id}", id);
|
||||
|
||||
return new { message = "Client updated successfully" };
|
||||
}
|
||||
|
||||
var postLogoutUris = updatedClient.PostLogoutRedirectUris ?? client.PostLogoutRedirectUris;
|
||||
foreach (var uri in postLogoutUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
|
||||
public async Task DeleteClientAsync(string id)
|
||||
{
|
||||
descriptor.PostLogoutRedirectUris.Add(new Uri(uri));
|
||||
}
|
||||
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
|
||||
var token = await GetAuthTokenAsync();
|
||||
|
||||
var grantTypes = updatedClient.GrantTypes ?? client.GrantTypes;
|
||||
foreach (var grantType in grantTypes)
|
||||
{
|
||||
descriptor.Permissions.Add(grantType);
|
||||
}
|
||||
var request = new HttpRequestMessage(HttpMethod.Delete, $"{authServiceUrl}/api/OAuthClients/{id}");
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
var scopes = updatedClient.Scopes ?? client.Scopes;
|
||||
foreach (var scope in scopes)
|
||||
{
|
||||
descriptor.Permissions.Add(scope);
|
||||
}
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
await _applicationManager.UpdateAsync(application, descriptor);
|
||||
|
||||
client.DisplayName = updatedClient.DisplayName ?? client.DisplayName;
|
||||
client.RedirectUris = redirectUris;
|
||||
client.PostLogoutRedirectUris = postLogoutUris;
|
||||
client.Scopes = scopes;
|
||||
client.GrantTypes = grantTypes;
|
||||
client.ClientType = updatedClient.ClientType ?? client.ClientType;
|
||||
client.ConsentType = updatedClient.ConsentType ?? client.ConsentType;
|
||||
client.Status = updatedClient.Status ?? client.Status;
|
||||
client.Description = updatedClient.Description ?? client.Description;
|
||||
client.UpdatedAt = DateTime.UtcNow;
|
||||
await _repository.UpdateAsync(client);
|
||||
|
||||
_logger.LogInformation("Updated OAuth client {ClientId}", client.ClientId);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public async Task DeleteClientAsync(long id)
|
||||
{
|
||||
var client = await _repository.GetByIdAsync(id);
|
||||
if (client == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"Client with ID {id} not found");
|
||||
}
|
||||
|
||||
var application = await _applicationManager.FindByClientIdAsync(client.ClientId);
|
||||
if (application != null)
|
||||
{
|
||||
await _applicationManager.DeleteAsync(application);
|
||||
_logger.LogInformation("Deleted OpenIddict application for client {ClientId}", client.ClientId);
|
||||
}
|
||||
|
||||
await _repository.DeleteAsync(client);
|
||||
|
||||
_logger.LogInformation("Deleted OAuth client {ClientId}", client.ClientId);
|
||||
_logger.LogInformation("Deleted OAuth client {Id}", id);
|
||||
}
|
||||
|
||||
public object GetClientOptions()
|
||||
@ -284,10 +296,38 @@ public class OAuthClientService : IOAuthClientService
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<string> GetAuthTokenAsync()
|
||||
{
|
||||
var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
|
||||
var clientId = _configuration["AuthService:ClientId"] ?? "console-client";
|
||||
var clientSecret = _configuration["AuthService:ClientSecret"] ?? "console-secret";
|
||||
|
||||
var requestBody = new Dictionary<string, string>
|
||||
{
|
||||
{ "grant_type", "client_credentials" },
|
||||
{ "client_id", clientId },
|
||||
{ "client_secret", clientSecret },
|
||||
{ "scope", "api" }
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/connect/token")
|
||||
{
|
||||
Content = new FormUrlEncodedContent(requestBody)
|
||||
};
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var tokenResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(content);
|
||||
|
||||
return tokenResponse!["access_token"].GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
private static string GenerateSecureSecret(int length = 32)
|
||||
{
|
||||
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var bytes = System.Security.Cryptography.RandomNumberGenerator.GetBytes(length);
|
||||
var bytes = RandomNumberGenerator.GetBytes(length);
|
||||
return new string(bytes.Select(b => chars[b % chars.Length]).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user