diff --git a/Configuration/FormValueRequiredAttribute.cs b/Configuration/FormValueRequiredAttribute.cs new file mode 100644 index 0000000..bdcef38 --- /dev/null +++ b/Configuration/FormValueRequiredAttribute.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; + +namespace Fengling.AuthService.Configuration; + +public sealed class FormValueRequiredAttribute(string name) : ActionMethodSelectorAttribute +{ + public override bool IsValidForRequest(RouteContext context, ActionDescriptor action) + { + if (string.Equals(context.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase) || + string.Equals(context.HttpContext.Request.Method, "HEAD", StringComparison.OrdinalIgnoreCase) || + string.Equals(context.HttpContext.Request.Method, "DELETE", StringComparison.OrdinalIgnoreCase) || + string.Equals(context.HttpContext.Request.Method, "TRACE", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (string.IsNullOrEmpty(context.HttpContext.Request.ContentType)) + { + return false; + } + + if (!context.HttpContext.Request.ContentType.StartsWith("application/x-www-form-urlencoded", + StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return !string.IsNullOrEmpty(context.HttpContext.Request.Form[name]); + } +} \ No newline at end of file diff --git a/Configuration/OpenIddictSetup.cs b/Configuration/OpenIddictSetup.cs index 7f6f5a2..34d4577 100644 --- a/Configuration/OpenIddictSetup.cs +++ b/Configuration/OpenIddictSetup.cs @@ -1,6 +1,9 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; +using Quartz; namespace Fengling.AuthService.Configuration; @@ -11,31 +14,84 @@ public static class OpenIddictSetup IConfiguration configuration ) { + + services.Configure(options => + { + // Configure Identity to use the same JWT claims as OpenIddict instead + // of the legacy WS-Federation claims it uses by default (ClaimTypes), + // which saves you from doing the mapping in your authorization controller. + options.ClaimsIdentity.UserNameClaimType = OpenIddictConstants.Claims.Name; + options.ClaimsIdentity.UserIdClaimType = OpenIddictConstants.Claims.Subject; + options.ClaimsIdentity.RoleClaimType = OpenIddictConstants.Claims.Role; + options.ClaimsIdentity.EmailClaimType = OpenIddictConstants.Claims.Email; + + // Note: to require account confirmation before login, + // register an email sender service (IEmailSender) and + // set options.SignIn.RequireConfirmedAccount to true. + // + // For more information, visit https://aka.ms/aspaccountconf. + options.SignIn.RequireConfirmedAccount = false; + }); + + services.AddQuartz(options => + { + options.UseSimpleTypeLoader(); + options.UseInMemoryStore(); + }); var isTesting = configuration.GetValue("Testing", false); var builder = services.AddOpenIddict(); builder.AddCore(options => { - options.UseEntityFrameworkCore().UseDbContext(); + options.UseEntityFrameworkCore() + .UseDbContext(); + options.UseQuartz(); }); if (!isTesting) { builder.AddServer(options => { - options.SetIssuer(configuration["OpenIddict:Issuer"] ?? "https://auth.fengling.local"); + options.SetIssuer(configuration["OpenIddict:Issuer"] ?? "http://localhost:5132"); + + options.SetAuthorizationEndpointUris("connect/authorize") + //.SetDeviceEndpointUris("connect/device") + .SetIntrospectionEndpointUris("connect/introspect") + .SetEndSessionEndpointUris("connect/endsession") + .SetTokenEndpointUris("connect/token") + .SetUserInfoEndpointUris("connect/userinfo") + .SetEndUserVerificationEndpointUris("connect/verify"); + + options.AllowAuthorizationCodeFlow() + .AllowHybridFlow() + .AllowClientCredentialsFlow() + .AllowRefreshTokenFlow(); + options.AddDevelopmentEncryptionCertificate() .AddDevelopmentSigningCertificate(); - options.RegisterScopes( - "openid", - "profile", - "email", + + options.DisableAccessTokenEncryption(); + + + options.RegisterScopes(OpenIddictConstants.Scopes.OfflineAccess, OpenIddictConstants.Scopes.Email, + OpenIddictConstants.Scopes.Profile, OpenIddictConstants.Scopes.OpenId, + OpenIddictConstants.Permissions.Scopes.Roles, "api", - "offline_access" - ); + "auth_server_admin"); + + options + .UseReferenceAccessTokens() + .UseReferenceRefreshTokens() + .UseAspNetCore() + .DisableTransportSecurityRequirement() + .EnableAuthorizationEndpointPassthrough() + .EnableEndSessionEndpointPassthrough() + .EnableTokenEndpointPassthrough() + .EnableUserInfoEndpointPassthrough() + .EnableStatusCodePagesIntegration(); }); } @@ -45,10 +101,7 @@ public static class OpenIddictSetup options.UseAspNetCore(); }); - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }); + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme); return services; } diff --git a/Controllers/AccountController.cs b/Controllers/AccountController.cs index 727e4a0..f9e82c4 100644 --- a/Controllers/AccountController.cs +++ b/Controllers/AccountController.cs @@ -51,7 +51,7 @@ public class AccountController : Controller return View(model); } - var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, false); + var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, true); if (!result.Succeeded) { if (result.IsLockedOut) @@ -117,13 +117,13 @@ public class AccountController : Controller [HttpGet("profile")] [HttpGet("settings")] - [HttpGet("logout")] + [HttpGet("~/connect/logout")] public IActionResult NotImplemented() { return RedirectToAction("Index", "Dashboard"); } - [HttpPost("logout")] + [HttpPost("~/connect/logout")] [ValidateAntiForgeryToken] public async Task LogoutPost() { diff --git a/Controllers/AuthorizationController.cs b/Controllers/AuthorizationController.cs index a1baff4..61ffcbd 100644 --- a/Controllers/AuthorizationController.cs +++ b/Controllers/AuthorizationController.cs @@ -7,15 +7,16 @@ using Microsoft.AspNetCore.Mvc; using OpenIddict.Abstractions; using OpenIddict.Server.AspNetCore; using System.Security.Claims; +using Fengling.AuthService.Configuration; using Fengling.AuthService.ViewModels; using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Primitives; +using Swashbuckle.AspNetCore.Annotations; using static OpenIddict.Abstractions.OpenIddictConstants; namespace Fengling.AuthService.Controllers; -[ApiController] -[Route("connect")] public class AuthorizationController( IOpenIddictApplicationManager applicationManager, IOpenIddictAuthorizationManager authorizationManager, @@ -25,9 +26,100 @@ public class AuthorizationController( ILogger logger) : Controller { + + [Authorize, FormValueRequired("submit.Accept")] + [Tags("submit.Accept")] + [HttpPost("~/connect/authorize"), ValidateAntiForgeryToken] + [SwaggerIgnore] + public async Task Accept() + { + var request = HttpContext.GetOpenIddictServerRequest() ?? + throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - [HttpGet("authorize")] - [HttpPost("authorize")] + // Retrieve the profile of the logged in user. + var user = await userManager.GetUserAsync(User) ?? + throw new InvalidOperationException("The user details cannot be retrieved."); + if (user == null) + { + throw new InvalidOperationException("The user details cannot be retrieved."); + } + + // Retrieve the application details from the database. + var application = await applicationManager.FindByClientIdAsync(request.ClientId!) ?? + throw new InvalidOperationException( + "Details concerning the calling client application cannot be found."); + + // Retrieve the permanent authorizations associated with the user and the calling client application. + var authorizations = await authorizationManager.FindAsync( + subject: await userManager.GetUserIdAsync(user), + client: await applicationManager.GetIdAsync(application), + status: OpenIddictConstants.Statuses.Valid, + type: OpenIddictConstants.AuthorizationTypes.Permanent, + scopes: request.GetScopes()).ToListAsync(); + + // Note: the same check is already made in the other action but is repeated + // here to ensure a malicious user can't abuse this POST-only endpoint and + // force it to return a valid response without the external authorization. + if (!authorizations.Any() && + await applicationManager.HasConsentTypeAsync(application, OpenIddictConstants.ConsentTypes.External)) + { + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.ConsentRequired, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = + "The logged in user is not allowed to access this client application." + }!)); + } + + var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); + + var principal = result.Principal!; + + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + principal.SetScopes(request.GetScopes()); + principal.SetResources(await scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); + + // Automatically create a permanent authorization to avoid requiring explicit consent + // for future authorization or token requests containing the same scopes. + var authorization = authorizations.LastOrDefault(); + if (authorization == null) + { + authorization = await authorizationManager.CreateAsync( + principal: principal, + subject: await userManager.GetUserIdAsync(user)!, + client: await applicationManager.GetIdAsync(application) ?? string.Empty, + type: OpenIddictConstants.AuthorizationTypes.Permanent, + scopes: principal.GetScopes()); + } + + principal.SetAuthorizationId(await authorizationManager.GetIdAsync(authorization)); + + foreach (var claim in principal.Claims) + { + claim.SetDestinations(GetDestinations(claim, principal)); + } + + // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. + return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } + + [Authorize, FormValueRequired("submit.Deny")] + [Tags("submit.Deny")] + [SwaggerIgnore] + [HttpPost("~/connect/authorize"), ValidateAntiForgeryToken] +// Notify OpenIddict that the authorization grant has been denied by the resource owner +// to redirect the user agent to the client application using the appropriate response_mode. + public IActionResult Deny() => Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + + + [HttpGet("~/connect/authorize")] + [HttpPost("~/connect/authorize")] + [Tags("Authorize")] + [IgnoreAntiforgeryToken] public async Task Authorize() { var request = HttpContext.GetOpenIddictServerRequest() ?? @@ -156,8 +248,10 @@ public class AuthorizationController( // At this point, no authorization was found in the database and an error must be returned // if the client application specified prompt=none in the authorization request. - case OpenIddictConstants.ConsentTypes.Explicit when request.HasPromptValue(OpenIddictConstants.PromptValues.None): - case OpenIddictConstants.ConsentTypes.Systematic when request.HasPromptValue(OpenIddictConstants.PromptValues.None): + case OpenIddictConstants.ConsentTypes.Explicit + when request.HasPromptValue(OpenIddictConstants.PromptValues.None): + case OpenIddictConstants.ConsentTypes.Systematic + when request.HasPromptValue(OpenIddictConstants.PromptValues.None): return Forbid( authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties: new AuthenticationProperties(new Dictionary @@ -170,13 +264,17 @@ public class AuthorizationController( // In every other case, render the consent form. default: - return View(new AuthorizeViewModel(await applicationManager.GetDisplayNameAsync(application),request.Scope)); + return View(new AuthorizeViewModel(await applicationManager.GetDisplayNameAsync(application), + request.Scope)); } } - + + + + private IEnumerable GetDestinations(Claim claim, ClaimsPrincipal principal) { - // Note: by default, claims are NOT automatically included in the access and identity tokens. + // Note: by default, claims are NOT automatically included in access and identity tokens. // To allow OpenIddict to serialize them, you must attach them a destination, that specifies // whether they should be included in access tokens, in identity tokens or in both. @@ -214,4 +312,56 @@ public class AuthorizationController( yield break; } } -} + + [Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)] + [HttpGet("~/connect/userinfo")] + public async Task UserInfo() + { + var user = await userManager.GetUserAsync(User) ?? + throw new InvalidOperationException("The user details cannot be retrieved."); + + // 获取用户的角色 + var roles = await userManager.GetRolesAsync(user); + + // 获取用户的租户信息 + var tenantInfo = user.TenantInfo; + + var claims = new List + { + new(OpenIddictConstants.Claims.Subject, await userManager.GetUserIdAsync(user)), + new(OpenIddictConstants.Claims.Name, user.UserName!), + new(OpenIddictConstants.Claims.PreferredUsername, user.UserName!) + }; + + if (!string.IsNullOrEmpty(user.Email)) + { + claims.Add(new(OpenIddictConstants.Claims.Email, user.Email!)); + } + + // 添加角色 claims + foreach (var role in roles) + { + claims.Add(new(OpenIddictConstants.Claims.Role, role)); + } + + // 添加自定义 tenant 相关 claims + if (tenantInfo != null) + { + claims.Add(new Claim("tenant_id", tenantInfo.Id.ToString())); + claims.Add(new Claim("tenant_code", tenantInfo.TenantId)); + claims.Add(new Claim("tenant_name", tenantInfo.Name)); + } + + return Ok(new Dictionary + { + ["sub"] = await userManager.GetUserIdAsync(user), + ["name"] = user.UserName, + ["preferred_username"] = user.UserName, + ["email"] = user.Email ?? "", + ["role"] = roles.ToArray(), + ["tenant_id"] = tenantInfo != null ? tenantInfo.Id.ToString() : "", + ["tenant_code"] = tenantInfo?.TenantId ?? "", + ["tenant_name"] = tenantInfo?.Name ?? "" + }); + } +} \ No newline at end of file diff --git a/Controllers/LogoutController.cs b/Controllers/LogoutController.cs index 502a4b6..ef6a810 100644 --- a/Controllers/LogoutController.cs +++ b/Controllers/LogoutController.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc; using OpenIddict.Abstractions; using OpenIddict.Server.AspNetCore; using static OpenIddict.Abstractions.OpenIddictConstants; +using System.Linq; namespace Fengling.AuthService.Controllers; @@ -43,29 +44,87 @@ public class LogoutController : ControllerBase var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("OpenIddict request is null"); + // 标准做法:先尝试从 id_token_hint 中提取客户端信息 + string? clientId = request.ClientId; + + if (string.IsNullOrEmpty(clientId) && !string.IsNullOrEmpty(request.IdTokenHint)) + { + try + { + // 从 id_token_hint 中提取 client_id (azp claim 或 aud claim) + var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; + if (principal != null) + { + // 尝试从 azp (authorized party) claim 获取 + clientId = principal.GetClaim(Claims.AuthorizedParty); + + // 如果没有 azp,尝试从 aud (audience) claim 获取 + if (string.IsNullOrEmpty(clientId)) + { + clientId = principal.GetClaim(Claims.Audience); + } + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to extract client_id from id_token_hint"); + } + } + + // 执行登出 var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); if (result.Succeeded) { await _signInManager.SignOutAsync(); } - if (request.ClientId != null) + // 处理 post_logout_redirect_uri + if (!string.IsNullOrEmpty(clientId)) { - var application = await _applicationManager.FindByClientIdAsync(request.ClientId); + var application = await _applicationManager.FindByClientIdAsync(clientId); if (application != null) { - var postLogoutRedirectUri = await _applicationManager.GetPostLogoutRedirectUrisAsync(application); + var registeredUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application); + + // 如果提供了 post_logout_redirect_uri,验证它是否在注册的 URI 列表中 if (!string.IsNullOrEmpty(request.PostLogoutRedirectUri)) { - if (postLogoutRedirectUri.Contains(request.PostLogoutRedirectUri)) + if (registeredUris.Contains(request.PostLogoutRedirectUri)) { - return Redirect(request.PostLogoutRedirectUri); + // 如果提供了 state,需要附加到重定向 URI + var redirectUri = request.PostLogoutRedirectUri; + if (!string.IsNullOrEmpty(request.State)) + { + var separator = redirectUri.Contains('?') ? "&" : "?"; + redirectUri = $"{redirectUri}{separator}state={Uri.EscapeDataString(request.State)}"; + } + + return Redirect(redirectUri); + } + else + { + _logger.LogWarning( + "Post-logout redirect URI {Uri} is not registered for client {ClientId}", + request.PostLogoutRedirectUri, + clientId); + } + } + else + { + // 如果没有提供 post_logout_redirect_uri,使用第一个注册的 URI + var defaultUri = registeredUris.FirstOrDefault(); + if (!string.IsNullOrEmpty(defaultUri)) + { + _logger.LogInformation( + "Using default post-logout redirect URI for client {ClientId}", + clientId); + return Redirect(defaultUri); } } } } - + // 如果无法确定重定向地址,返回默认页面 return Redirect("/"); } } diff --git a/Controllers/OAuthClientsController.cs b/Controllers/OAuthClientsController.cs index 86a4964..6f1d7f8 100644 --- a/Controllers/OAuthClientsController.cs +++ b/Controllers/OAuthClientsController.cs @@ -1,10 +1,6 @@ -using Fengling.AuthService.Data; -using Fengling.AuthService.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using OpenIddict.Abstractions; -using System.Security.Claims; using System.Security.Cryptography; namespace Fengling.AuthService.Controllers; @@ -14,16 +10,13 @@ namespace Fengling.AuthService.Controllers; [Authorize] public class OAuthClientsController : ControllerBase { - private readonly ApplicationDbContext _context; private readonly IOpenIddictApplicationManager _applicationManager; private readonly ILogger _logger; public OAuthClientsController( - ApplicationDbContext context, IOpenIddictApplicationManager applicationManager, ILogger logger) { - _context = context; _applicationManager = applicationManager; _logger = logger; } @@ -33,54 +26,60 @@ public class OAuthClientsController : ControllerBase [FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] string? displayName = null, - [FromQuery] string? clientId = null, - [FromQuery] string? status = null) + [FromQuery] string? clientId = null) { - var query = _context.OAuthApplications.AsQueryable(); + var applications = _applicationManager.ListAsync(); + var clientList = new List(); - if (!string.IsNullOrEmpty(displayName)) + await foreach (var application in applications) { - query = query.Where(c => c.DisplayName.Contains(displayName)); + 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 redirectUrisStrings = await _applicationManager.GetRedirectUrisAsync(application); + var postLogoutRedirectUrisStrings = await _applicationManager.GetPostLogoutRedirectUrisAsync(application); + var redirectUris = redirectUrisStrings; + var postLogoutRedirectUris = postLogoutRedirectUrisStrings; + + clientList.Add(new + { + id = application, + clientId = clientIdValue, + displayName = displayNameValue, + redirectUris = redirectUris.Select(u => u.ToString()).ToArray(), + postLogoutRedirectUris = postLogoutRedirectUris.Select(u => u.ToString()).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", + permissions + }); } - if (!string.IsNullOrEmpty(clientId)) - { - query = query.Where(c => c.ClientId.Contains(clientId)); - } - - if (!string.IsNullOrEmpty(status)) - { - query = query.Where(c => c.Status == status); - } - - var totalCount = await query.CountAsync(); - var clients = await query - .OrderByDescending(c => c.CreatedAt) + var sortedClients = clientList + .OrderByDescending(c => (c as dynamic).clientId) .Skip((page - 1) * pageSize) .Take(pageSize) - .ToListAsync(); - - var result = clients.Select(c => new - { - 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, - }); + .ToList(); return Ok(new { - items = result, - totalCount, + items = sortedClients, + totalCount = clientList.Count, page, pageSize }); @@ -127,36 +126,46 @@ public class OAuthClientsController : ControllerBase } [HttpGet("{id}")] - public async Task> GetClient(long id) + public async Task> GetClient(string id) { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) + var application = await _applicationManager.FindByIdAsync(id); + if (application == null) { return NotFound(); } + 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); + return Ok(new { - 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, + id = id, + clientId = clientIdValue, + displayName = displayNameValue, + redirectUris = redirectUris.Select(u => u.ToString()).ToArray(), + postLogoutRedirectUris = postLogoutRedirectUris.Select(u => u.ToString()).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", + permissions }); } [HttpPost] public async Task> CreateClient([FromBody] CreateOAuthClientDto dto) { - var existingClient = await _context.OAuthApplications.FirstOrDefaultAsync(c => c.ClientId == dto.ClientId); + var existingClient = await _applicationManager.FindByClientIdAsync(dto.ClientId); if (existingClient != null) { return BadRequest(new { message = "Client ID 已存在", clientId = dto.ClientId }); @@ -168,9 +177,16 @@ public class OAuthClientsController : ControllerBase { ClientId = dto.ClientId, ClientSecret = clientSecret, - DisplayName = dto.DisplayName + DisplayName = dto.DisplayName, + ClientType = string.IsNullOrEmpty(dto.ClientType) ? "confidential" : dto.ClientType, + ConsentType = string.IsNullOrEmpty(dto.ConsentType) ? "explicit" : dto.ConsentType, }; + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization); + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.EndSession); + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token); + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection); + foreach (var uri in (dto.RedirectUris ?? Array.Empty()).Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) { descriptor.RedirectUris.Add(new Uri(uri)); @@ -183,205 +199,196 @@ public class OAuthClientsController : ControllerBase foreach (var grantType in dto.GrantTypes ?? Array.Empty()) { - descriptor.Permissions.Add(grantType); + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.GrantType + grantType); } foreach (var scope in dto.Scopes ?? Array.Empty()) { - descriptor.Permissions.Add(scope); + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope); } - await _applicationManager.CreateAsync(descriptor); + var application = await _applicationManager.CreateAsync(descriptor); + var applicationId = await _applicationManager.GetIdAsync(application); - var client = new OAuthApplication + return CreatedAtAction(nameof(GetClient), new { id = applicationId }, new { - ClientId = dto.ClientId, - ClientSecret = clientSecret, - DisplayName = dto.DisplayName, - RedirectUris = dto.RedirectUris ?? Array.Empty(), - PostLogoutRedirectUris = dto.PostLogoutRedirectUris ?? Array.Empty(), - 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, - }; - - _context.OAuthApplications.Add(client); - await _context.SaveChangesAsync(); - - await CreateAuditLog("oauth", "create", "OAuthClient", client.Id, client.DisplayName, null, SerializeToJson(dto)); - - return CreatedAtAction(nameof(GetClient), new { id = client.Id }, new - { - client.Id, - client.ClientId, - client.ClientSecret, - client.DisplayName, - client.Status, - client.CreatedAt + id = applicationId, + clientId = dto.ClientId, + clientSecret = clientSecret, + displayName = dto.DisplayName, + status = "active" }); } [HttpPost("{id}/generate-secret")] - public async Task> GenerateSecret(long id) + public async Task> GenerateSecret(string id) { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) - { - return NotFound(); - } - - var newSecret = GenerateSecureSecret(); - client.ClientSecret = newSecret; - client.UpdatedAt = DateTime.UtcNow; - - var application = await _applicationManager.FindByClientIdAsync(client.ClientId); - if (application != null) - { - var descriptor = new OpenIddictApplicationDescriptor - { - ClientId = client.ClientId, - ClientSecret = newSecret, - DisplayName = client.DisplayName - }; - - await _applicationManager.UpdateAsync(application, descriptor); - } - - await _context.SaveChangesAsync(); - - await CreateAuditLog("oauth", "generate_secret", "OAuthClient", client.Id, client.DisplayName, "[REDACTED]", "[REDACTED]"); - - return Ok(new - { - clientId = client.ClientId, - clientSecret = newSecret, - message = "新密钥已生成,请妥善保管,刷新后将无法再次查看" - }); - } - - [HttpPost("{id}/toggle-status")] - public async Task> ToggleStatus(long id) - { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) - { - return NotFound(); - } - - var oldStatus = client.Status; - client.Status = client.Status == "active" ? "inactive" : "active"; - client.UpdatedAt = DateTime.UtcNow; - - await _context.SaveChangesAsync(); - - await CreateAuditLog("oauth", "toggle_status", "OAuthClient", client.Id, client.DisplayName, oldStatus, client.Status); - - return Ok(new - { - clientId = client.ClientId, - oldStatus, - newStatus = client.Status, - message = $"客户端状态已从 {oldStatus} 更改为 {client.Status}" - }); - } - - [HttpPut("{id}")] - public async Task UpdateClient(long id, [FromBody] UpdateOAuthClientDto dto) - { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) - { - return NotFound(); - } - - var application = await _applicationManager.FindByClientIdAsync(client.ClientId); + var application = await _applicationManager.FindByIdAsync(id); if (application == null) { return NotFound(); } + 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 newSecret = GenerateSecureSecret(); + var descriptor = new OpenIddictApplicationDescriptor { - ClientId = client.ClientId, - ClientSecret = client.ClientSecret, - DisplayName = dto.DisplayName ?? client.DisplayName + ClientId = clientIdValue, + ClientSecret = newSecret, + DisplayName = displayNameValue, + ClientType = clientType, + ConsentType = consentType, }; - var redirectUris = dto.RedirectUris ?? client.RedirectUris; - foreach (var uri in redirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) + foreach (var permission in permissions) { - descriptor.RedirectUris.Add(new Uri(uri)); + descriptor.Permissions.Add(permission); } - var postLogoutUris = dto.PostLogoutRedirectUris ?? client.PostLogoutRedirectUris; - foreach (var uri in postLogoutUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) + foreach (var uriString in redirectUris) { - descriptor.PostLogoutRedirectUris.Add(new Uri(uri)); + if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri)) + { + descriptor.RedirectUris.Add(uri); + } } - var grantTypes = dto.GrantTypes ?? client.GrantTypes; - foreach (var grantType in grantTypes) + foreach (var uriString in postLogoutRedirectUris) { - descriptor.Permissions.Add(grantType); - } - - var scopes = dto.Scopes ?? client.Scopes; - foreach (var scope in scopes) - { - descriptor.Permissions.Add(scope); + if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri)) + { + descriptor.PostLogoutRedirectUris.Add(uri); + } } await _applicationManager.UpdateAsync(application, descriptor); - client.DisplayName = dto.DisplayName ?? client.DisplayName; - client.RedirectUris = redirectUris; - client.PostLogoutRedirectUris = postLogoutUris; - client.Scopes = scopes; - client.GrantTypes = grantTypes; - client.ClientType = dto.ClientType ?? client.ClientType; - client.ConsentType = dto.ConsentType ?? client.ConsentType; - client.Status = dto.Status ?? client.Status; - client.Description = dto.Description ?? client.Description; - client.UpdatedAt = DateTime.UtcNow; - - await _context.SaveChangesAsync(); - - await CreateAuditLog("oauth", "update", "OAuthClient", client.Id, client.DisplayName, null, SerializeToJson(client)); - - return NoContent(); + return Ok(new + { + clientId = clientIdValue, + clientSecret = newSecret, + message = "新密钥已生成,请妥善保管,刷新后将无法再次查看" + }); } [HttpDelete("{id}")] - public async Task DeleteClient(long id) + public async Task DeleteClient(string id) { - var client = await _context.OAuthApplications.FindAsync(id); - if (client == null) + var application = await _applicationManager.FindByIdAsync(id); + if (application == null) { return NotFound(); } + var clientIdValue = await _applicationManager.GetClientIdAsync(application); + try { - var application = await _applicationManager.FindByClientIdAsync(client.ClientId); - if (application != null) - { - await _applicationManager.DeleteAsync(application); - _logger.LogInformation("Deleted OpenIddict application {ClientId}", client.ClientId); - } + await _applicationManager.DeleteAsync(application); + _logger.LogInformation("Deleted OpenIddict application {ClientId}", clientIdValue); } catch (Exception ex) { - _logger.LogWarning(ex, "Failed to delete OpenIddict application for client {ClientId}", client.ClientId); + _logger.LogWarning(ex, "Failed to delete OpenIddict application for client {ClientId}", clientIdValue); + return BadRequest(new { message = ex.Message }); } - _context.OAuthApplications.Remove(client); - await _context.SaveChangesAsync(); + return NoContent(); + } - await CreateAuditLog("oauth", "delete", "OAuthClient", client.Id, client.DisplayName, SerializeToJson(client)); + [HttpPut("{id}")] + public async Task UpdateClient(string id, [FromBody] UpdateOAuthClientDto dto) + { + var application = await _applicationManager.FindByIdAsync(id); + if (application == null) + { + return NotFound(); + } + + 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 redirectUris = await _applicationManager.GetRedirectUrisAsync(application); + var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application); + var existingPermissions = await _applicationManager.GetPermissionsAsync(application); + + var descriptor = new OpenIddictApplicationDescriptor + { + ClientId = clientIdValue, + DisplayName = dto.DisplayName ?? displayNameValue, + ClientType = string.IsNullOrEmpty(dto.ClientType) ? clientType?.ToString() : dto.ClientType, + ConsentType = string.IsNullOrEmpty(dto.ConsentType) ? consentType?.ToString() : dto.ConsentType, + }; + + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization); + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.EndSession); + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token); + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection); + + if (dto.RedirectUris != null) + { + foreach (var uri in dto.RedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) + { + descriptor.RedirectUris.Add(new Uri(uri)); + } + } + else + { + foreach (var uriString in redirectUris) + { + if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri)) + { + descriptor.RedirectUris.Add(uri); + } + } + } + + if (dto.PostLogoutRedirectUris != null) + { + foreach (var uri in dto.PostLogoutRedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) + { + descriptor.PostLogoutRedirectUris.Add(new Uri(uri)); + } + } + else + { + foreach (var uriString in postLogoutRedirectUris) + { + if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri)) + { + descriptor.PostLogoutRedirectUris.Add(uri); + } + } + } + + var grantTypes = dto.GrantTypes ?? existingPermissions + .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType)) + .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)); + + var scopes = dto.Scopes ?? existingPermissions + .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope)) + .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length)); + + foreach (var grantType in grantTypes) + { + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.GrantType + grantType); + } + + foreach (var scope in scopes) + { + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope); + } + + await _applicationManager.UpdateAsync(application, descriptor); return NoContent(); } @@ -392,38 +399,6 @@ public class OAuthClientsController : ControllerBase var bytes = RandomNumberGenerator.GetBytes(length); return new string(bytes.Select(b => chars[b % chars.Length]).ToArray()); } - - private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null) - { - var userName = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.Identity?.Name ?? "system"; - var tenantId = User.FindFirstValue("TenantId"); - - var log = new AuditLog - { - Operator = userName, - TenantId = tenantId, - Operation = operation, - Action = action, - TargetType = targetType, - TargetId = targetId, - TargetName = targetName, - IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown", - Status = "success", - OldValue = oldValue, - NewValue = newValue, - }; - - _context.AuditLogs.Add(log); - await _context.SaveChangesAsync(); - } - - private static string SerializeToJson(object obj) - { - return System.Text.Json.JsonSerializer.Serialize(obj, new System.Text.Json.JsonSerializerOptions - { - WriteIndented = false - }); - } } public class CreateOAuthClientDto diff --git a/Controllers/StatsController.cs b/Controllers/StatsController.cs index c665ee3..90ce1aa 100644 --- a/Controllers/StatsController.cs +++ b/Controllers/StatsController.cs @@ -3,6 +3,7 @@ using Fengling.AuthService.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using OpenIddict.Abstractions; namespace Fengling.AuthService.Controllers; @@ -12,13 +13,16 @@ namespace Fengling.AuthService.Controllers; public class StatsController : ControllerBase { private readonly ApplicationDbContext _context; + private readonly IOpenIddictApplicationManager _applicationManager; private readonly ILogger _logger; public StatsController( ApplicationDbContext context, + IOpenIddictApplicationManager applicationManager, ILogger logger) { _context = context; + _applicationManager = applicationManager; _logger = logger; } @@ -30,7 +34,7 @@ public class StatsController : ControllerBase var userCount = await _context.Users.CountAsync(u => !u.IsDeleted); var tenantCount = await _context.Tenants.CountAsync(t => !t.IsDeleted); - var oauthClientCount = await _context.OAuthApplications.CountAsync(); + var oauthClientCount = await CountOAuthClientsAsync(); var todayAccessCount = await _context.AccessLogs .CountAsync(l => l.CreatedAt >= today && l.CreatedAt < tomorrow); @@ -43,6 +47,17 @@ public class StatsController : ControllerBase }); } + private async Task CountOAuthClientsAsync() + { + var count = 0; + var applications = _applicationManager.ListAsync(); + await foreach (var _ in applications) + { + count++; + } + return count; + } + [HttpGet("system")] public ActionResult GetSystemStats() { diff --git a/Controllers/TokenController.cs b/Controllers/TokenController.cs index 03fc7f9..22433e7 100644 --- a/Controllers/TokenController.cs +++ b/Controllers/TokenController.cs @@ -23,14 +23,13 @@ public class TokenController( ILogger logger) : ControllerBase { - private readonly ILogger _logger = logger; [HttpPost("token")] public async Task Exchange() { var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("OpenIddict request is null"); - var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); + var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); if (request.IsAuthorizationCodeGrantType()) { @@ -110,6 +109,12 @@ public class TokenController( var identity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); + // 设置 Claim Destinations + foreach (var claim in principal.Claims) + { + claim.SetDestinations(GetDestinations(claim, principal)); + } + principal.SetScopes(request.GetScopes()); principal.SetResources(await scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); principal.SetAuthorizationId(await authorizationManager.GetIdAsync(authorization)); @@ -199,8 +204,14 @@ public class TokenController( yield break; + // 明确处理租户 ID - 这是业务关键信息 + case "tenant_id": + yield return OpenIddictConstants.Destinations.AccessToken; + yield break; + // Never include the security stamp in the access and identity tokens, as it's a secret value. - case "AspNet.Identity.SecurityStamp": yield break; + case "AspNet.Identity.SecurityStamp": + yield break; default: yield return OpenIddictConstants.Destinations.AccessToken; @@ -247,6 +258,12 @@ public class TokenController( var identity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); + // 设置 Claim Destinations + foreach (var claim in principal.Claims) + { + claim.SetDestinations(GetDestinations(claim, principal)); + } + principal.SetScopes(request.GetScopes()); principal.SetResources(await scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); diff --git a/Data/ApplicationDbContext.cs b/Data/ApplicationDbContext.cs index 23b0f93..04f5eab 100644 --- a/Data/ApplicationDbContext.cs +++ b/Data/ApplicationDbContext.cs @@ -11,7 +11,6 @@ public class ApplicationDbContext : IdentityDbContext OAuthApplications { get; set; } public DbSet Tenants { get; set; } public DbSet AccessLogs { get; set; } public DbSet AuditLogs { get; set; } @@ -35,23 +34,7 @@ public class ApplicationDbContext : IdentityDbContext(entity => - { - entity.Property(e => e.Description).HasMaxLength(200); - }); - - builder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.HasIndex(e => e.ClientId).IsUnique(); - entity.Property(e => e.ClientId).HasMaxLength(100); - entity.Property(e => e.ClientSecret).HasMaxLength(200); - entity.Property(e => e.DisplayName).HasMaxLength(100); - entity.Property(e => e.ClientType).HasMaxLength(20); - entity.Property(e => e.ConsentType).HasMaxLength(20); - entity.Property(e => e.Status).HasMaxLength(20); - entity.Property(e => e.Description).HasMaxLength(500); - }); + builder.Entity(entity => { entity.Property(e => e.Description).HasMaxLength(200); }); builder.Entity(entity => { @@ -103,4 +86,4 @@ public class ApplicationDbContext : IdentityDbContext e.Status).HasMaxLength(20); }); } -} +} \ No newline at end of file diff --git a/Data/ApplicationDbContextFactory.cs b/Data/ApplicationDbContextFactory.cs index 62c3423..36fc463 100644 --- a/Data/ApplicationDbContextFactory.cs +++ b/Data/ApplicationDbContextFactory.cs @@ -10,7 +10,7 @@ public class ApplicationDbContextFactory : IDesignTimeDbContextFactory(); optionsBuilder.UseNpgsql("Host=192.168.100.10;Port=5432;Database=fengling_auth;Username=movingsam;Password=sl52788542"); - + optionsBuilder.UseOpenIddict(); return new ApplicationDbContext(optionsBuilder.Options); } } diff --git a/Data/SeedData.cs b/Data/SeedData.cs index 38274fa..8e1d900 100644 --- a/Data/SeedData.cs +++ b/Data/SeedData.cs @@ -1,6 +1,7 @@ using Fengling.AuthService.Models; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using OpenIddict.Abstractions; namespace Fengling.AuthService.Data; @@ -12,6 +13,8 @@ public static class SeedData var context = scope.ServiceProvider.GetRequiredService(); var userManager = scope.ServiceProvider.GetRequiredService>(); var roleManager = scope.ServiceProvider.GetRequiredService>(); + var applicationManager = scope.ServiceProvider.GetRequiredService(); + var scopeManager = scope.ServiceProvider.GetRequiredService(); await context.Database.EnsureCreatedAsync(); @@ -119,32 +122,70 @@ public static class SeedData } } - var consoleClient = await context.OAuthApplications - .FirstOrDefaultAsync(c => c.ClientId == "fengling-console"); + var consoleClient = await applicationManager.FindByClientIdAsync("fengling-console"); + if (consoleClient == null) { - consoleClient = new OAuthApplication + var descriptor = new OpenIddictApplicationDescriptor { ClientId = "fengling-console", - ClientSecret = null, DisplayName = "Fengling Console", - RedirectUris = new[] { - "http://localhost:5777/auth/callback", - "https://console.fengling.local/auth/callback" - }, - PostLogoutRedirectUris = new[] { - "http://localhost:5777/", - "https://console.fengling.local/" - }, - Scopes = new[] { "api", "offline_access", "openid", "profile", "email" }, - GrantTypes = new[] { "authorization_code", "refresh_token" }, - ClientType = "public", - ConsentType = "implicit", - Status = "active", - CreatedAt = DateTime.UtcNow + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.EndSession, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Introspection + } }; - context.OAuthApplications.Add(consoleClient); - await context.SaveChangesAsync(); + + foreach (var uri in new[] + { + "http://localhost:5777/auth/callback", + "https://console.fengling.local/auth/callback" + }) + { + descriptor.RedirectUris.Add(new Uri(uri)); + } + + foreach (var uri in new[] + { + "http://localhost:5777/", + "https://console.fengling.local/" + }) + { + descriptor.PostLogoutRedirectUris.Add(new Uri(uri)); + } + + descriptor.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Code); + + var scopes = new[] + { + OpenIddictConstants.Permissions.Prefixes.Scope + "api", + OpenIddictConstants.Permissions.Prefixes.Scope + OpenIddictConstants.Scopes.OfflineAccess, + OpenIddictConstants.Permissions.Prefixes.Scope + OpenIddictConstants.Scopes.OpenId, + OpenIddictConstants.Permissions.Prefixes.Scope + OpenIddictConstants.Scopes.Profile, + OpenIddictConstants.Permissions.Prefixes.Scope + OpenIddictConstants.Scopes.Roles, + OpenIddictConstants.Permissions.Prefixes.Scope + OpenIddictConstants.Scopes.Email + }; + + foreach (var permissionScope in scopes) + { + descriptor.Permissions.Add(permissionScope); + } + + var grantTypes = new[] + { + OpenIddictConstants.Permissions.Prefixes.GrantType + OpenIddictConstants.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.Prefixes.GrantType + OpenIddictConstants.GrantTypes.RefreshToken + }; + + foreach (var grantType in grantTypes) + { + descriptor.Permissions.Add(grantType); + } + + await applicationManager.CreateAsync(descriptor); } } } diff --git a/Fengling.AuthService.csproj b/Fengling.AuthService.csproj index 9fb5156..c3d5d61 100644 --- a/Fengling.AuthService.csproj +++ b/Fengling.AuthService.csproj @@ -6,6 +6,7 @@ + all diff --git a/Migrations/20260205165820_InitialCreate.Designer.cs b/Migrations/20260206142720_inital.Designer.cs similarity index 71% rename from Migrations/20260205165820_InitialCreate.Designer.cs rename to Migrations/20260206142720_inital.Designer.cs index 853405c..b62e558 100644 --- a/Migrations/20260205165820_InitialCreate.Designer.cs +++ b/Migrations/20260206142720_inital.Designer.cs @@ -13,8 +13,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Fengling.AuthService.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20260205165820_InitialCreate")] - partial class InitialCreate + [Migration("20260206142720_inital")] + partial class inital { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -550,6 +550,214 @@ namespace Fengling.AuthService.Migrations b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b => { b.OwnsOne("Fengling.AuthService.Models.TenantInfo", "TenantInfo", b1 => @@ -633,6 +841,42 @@ namespace Fengling.AuthService.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); #pragma warning restore 612, 618 } } diff --git a/Migrations/20260205165820_InitialCreate.cs b/Migrations/20260206142720_inital.cs similarity index 71% rename from Migrations/20260205165820_InitialCreate.cs rename to Migrations/20260206142720_inital.cs index 99261d1..7948300 100644 --- a/Migrations/20260205165820_InitialCreate.cs +++ b/Migrations/20260206142720_inital.cs @@ -8,7 +8,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Fengling.AuthService.Migrations { /// - public partial class InitialCreate : Migration + public partial class inital : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) @@ -144,6 +144,51 @@ namespace Fengling.AuthService.Migrations table.PrimaryKey("PK_OAuthApplications", x => x.Id); }); + migrationBuilder.CreateTable( + name: "OpenIddictApplications", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + ApplicationType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + ClientId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + ClientSecret = table.Column(type: "text", nullable: true), + ClientType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + ConcurrencyToken = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + ConsentType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + DisplayName = table.Column(type: "text", nullable: true), + DisplayNames = table.Column(type: "text", nullable: true), + JsonWebKeySet = table.Column(type: "text", nullable: true), + Permissions = table.Column(type: "text", nullable: true), + PostLogoutRedirectUris = table.Column(type: "text", nullable: true), + Properties = table.Column(type: "text", nullable: true), + RedirectUris = table.Column(type: "text", nullable: true), + Requirements = table.Column(type: "text", nullable: true), + Settings = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictApplications", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictScopes", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + ConcurrencyToken = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Description = table.Column(type: "text", nullable: true), + Descriptions = table.Column(type: "text", nullable: true), + DisplayName = table.Column(type: "text", nullable: true), + DisplayNames = table.Column(type: "text", nullable: true), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Properties = table.Column(type: "text", nullable: true), + Resources = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictScopes", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Tenants", columns: table => new @@ -274,6 +319,63 @@ namespace Fengling.AuthService.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "OpenIddictAuthorizations", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + ApplicationId = table.Column(type: "text", nullable: true), + ConcurrencyToken = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + CreationDate = table.Column(type: "timestamp with time zone", nullable: true), + Properties = table.Column(type: "text", nullable: true), + Scopes = table.Column(type: "text", nullable: true), + Status = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Subject = table.Column(type: "character varying(400)", maxLength: 400, nullable: true), + Type = table.Column(type: "character varying(50)", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictAuthorizations", x => x.Id); + table.ForeignKey( + name: "FK_OpenIddictAuthorizations_OpenIddictApplications_Application~", + column: x => x.ApplicationId, + principalTable: "OpenIddictApplications", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictTokens", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + ApplicationId = table.Column(type: "text", nullable: true), + AuthorizationId = table.Column(type: "text", nullable: true), + ConcurrencyToken = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + CreationDate = table.Column(type: "timestamp with time zone", nullable: true), + ExpirationDate = table.Column(type: "timestamp with time zone", nullable: true), + Payload = table.Column(type: "text", nullable: true), + Properties = table.Column(type: "text", nullable: true), + RedemptionDate = table.Column(type: "timestamp with time zone", nullable: true), + ReferenceId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Status = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Subject = table.Column(type: "character varying(400)", maxLength: 400, nullable: true), + Type = table.Column(type: "character varying(150)", maxLength: 150, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictTokens", x => x.Id); + table.ForeignKey( + name: "FK_OpenIddictTokens_OpenIddictApplications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "OpenIddictApplications", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_OpenIddictTokens_OpenIddictAuthorizations_AuthorizationId", + column: x => x.AuthorizationId, + principalTable: "OpenIddictAuthorizations", + principalColumn: "Id"); + }); + migrationBuilder.CreateIndex( name: "IX_AccessLogs_Action", table: "AccessLogs", @@ -373,6 +475,39 @@ namespace Fengling.AuthService.Migrations column: "ClientId", unique: true); + migrationBuilder.CreateIndex( + name: "IX_OpenIddictApplications_ClientId", + table: "OpenIddictApplications", + column: "ClientId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictAuthorizations_ApplicationId_Status_Subject_Type", + table: "OpenIddictAuthorizations", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictScopes_Name", + table: "OpenIddictScopes", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_ApplicationId_Status_Subject_Type", + table: "OpenIddictTokens", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_AuthorizationId", + table: "OpenIddictTokens", + column: "AuthorizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_ReferenceId", + table: "OpenIddictTokens", + column: "ReferenceId", + unique: true); + migrationBuilder.CreateIndex( name: "IX_Tenants_TenantId", table: "Tenants", @@ -407,6 +542,12 @@ namespace Fengling.AuthService.Migrations migrationBuilder.DropTable( name: "OAuthApplications"); + migrationBuilder.DropTable( + name: "OpenIddictScopes"); + + migrationBuilder.DropTable( + name: "OpenIddictTokens"); + migrationBuilder.DropTable( name: "Tenants"); @@ -415,6 +556,12 @@ namespace Fengling.AuthService.Migrations migrationBuilder.DropTable( name: "AspNetUsers"); + + migrationBuilder.DropTable( + name: "OpenIddictAuthorizations"); + + migrationBuilder.DropTable( + name: "OpenIddictApplications"); } } } diff --git a/Migrations/ApplicationDbContextModelSnapshot.cs b/Migrations/ApplicationDbContextModelSnapshot.cs index 7040b43..24d7c3b 100644 --- a/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Migrations/ApplicationDbContextModelSnapshot.cs @@ -547,6 +547,214 @@ namespace Fengling.AuthService.Migrations b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b => { b.OwnsOne("Fengling.AuthService.Models.TenantInfo", "TenantInfo", b1 => @@ -630,6 +838,42 @@ namespace Fengling.AuthService.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); #pragma warning restore 612, 618 } } diff --git a/Models/OAuthApplication.cs b/Models/OAuthApplication.cs deleted file mode 100644 index 59d3b11..0000000 --- a/Models/OAuthApplication.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Fengling.AuthService.Models; - -public class OAuthApplication -{ - public long Id { get; set; } - public string ClientId { get; set; } = string.Empty; - public string? ClientSecret { get; set; } - public string DisplayName { get; set; } = string.Empty; - public string[] RedirectUris { get; set; } = Array.Empty(); - public string[] PostLogoutRedirectUris { get; set; } = Array.Empty(); - public string[] Scopes { get; set; } = Array.Empty(); - public string[] GrantTypes { get; set; } = Array.Empty(); - public string ClientType { get; set; } = "public"; - public string ConsentType { get; set; } = "implicit"; - public string Status { get; set; } = "active"; - public string? Description { get; set; } - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime? UpdatedAt { get; set; } -} diff --git a/Program.cs b/Program.cs index 3640d60..d887dcb 100644 --- a/Program.cs +++ b/Program.cs @@ -23,14 +23,8 @@ builder.Host.UseSerilog(); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext(options => { - if (connectionString.StartsWith("DataSource=")) - { - options.UseInMemoryDatabase(connectionString); - } - else - { - options.UseNpgsql(connectionString); - } + options.UseNpgsql(connectionString); + options.UseOpenIddict(); }); builder.Services.AddRazorPages(); @@ -96,6 +90,14 @@ using (var scope = app.Services.CreateScope()) await SeedData.Initialize(scope.ServiceProvider); } +app.UseCors(x => +{ + x.SetIsOriginAllowed(origin => true) + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials() + .Build(); +}); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); diff --git a/ViewModels/RegisterViewModel.cs b/ViewModels/RegisterViewModel.cs index 555da86..17af409 100644 --- a/ViewModels/RegisterViewModel.cs +++ b/ViewModels/RegisterViewModel.cs @@ -6,21 +6,21 @@ public class RegisterViewModel { [Required(ErrorMessage = "用户名不能为空")] [StringLength(50, MinimumLength = 3, ErrorMessage = "用户名长度必须在3-50个字符之间")] - public string Username { get; set; } + public string Username { get; set; } = default!; [Required(ErrorMessage = "邮箱不能为空")] [EmailAddress(ErrorMessage = "请输入有效的邮箱地址")] - public string Email { get; set; } + public string Email { get; set; }= default!; [Required(ErrorMessage = "密码不能为空")] [StringLength(100, MinimumLength = 6, ErrorMessage = "密码长度必须在6-100个字符之间")] [DataType(DataType.Password)] - public string Password { get; set; } + public string Password { get; set; }= default!; [Required(ErrorMessage = "确认密码不能为空")] [DataType(DataType.Password)] [Compare("Password", ErrorMessage = "两次输入的密码不一致")] - public string ConfirmPassword { get; set; } + public string ConfirmPassword { get; set; }= default!; - public string ReturnUrl { get; set; } + public string ReturnUrl { get; set; }= default!; } \ No newline at end of file diff --git a/Views/Authorization/Authorize.cshtml b/Views/Authorization/Authorize.cshtml index dc4dda9..7c8efdd 100644 --- a/Views/Authorization/Authorize.cshtml +++ b/Views/Authorization/Authorize.cshtml @@ -1,3 +1,4 @@ +@using Microsoft.Extensions.Primitives @model Fengling.AuthService.ViewModels.AuthorizeViewModel @{ @@ -10,13 +11,14 @@
- +

授权确认

- @Model.ApplicationName + @Model.ApplicationName 请求访问您的账户

@@ -27,7 +29,8 @@
-
+
@(Model.ApplicationName?.Substring(0, Math.Min(1, Model.ApplicationName.Length)).ToUpper() ?? "A")
@@ -49,7 +52,10 @@ @foreach (var scope in Model.Scopes) {
- + @@ -70,7 +76,9 @@
- + @@ -83,19 +91,28 @@
-
+ + @* Flow the request parameters so they can be received by the Accept/Reject actions: *@ + @foreach (var parameter in Context.Request.HasFormContentType ? + (IEnumerable>) Context.Request.Form : Context.Request.Query) + { + + } +
-
@functions { + private string GetScopeDisplayName(string scope) { return scope switch @@ -143,4 +161,5 @@ _ => "自定义权限范围" }; } + } diff --git a/appsettings.Development.json b/appsettings.Development.json index c721c91..2f75d11 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "DataSource=:memory:" + "DefaultConnection": "Host=192.168.100.10;Port=5432;Database=fengling_auth;Username=movingsam;Password=sl52788542" }, "Logging": { "LogLevel": { diff --git a/appsettings.json b/appsettings.json index 14eeb16..3109c49 100644 --- a/appsettings.json +++ b/appsettings.json @@ -3,12 +3,12 @@ "DefaultConnection": "Host=192.168.100.10;Port=5432;Database=fengling_auth;Username=movingsam;Password=sl52788542" }, "Jwt": { - "Issuer": "https://auth.fengling.local", + "Issuer": "http://localhost:5132", "Audience": "fengling-api", "Secret": "FenglingAuthSecretKey2024!ChangeThisInProduction!" }, "OpenIddict": { - "Issuer": "https://auth.fengling.local", + "Issuer": "http://localhost:5132", "Audience": "fengling-api" }, "Logging": {