From f1e28e0ebcca3c9c11c8f6d98950b70f4bee55a2 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 21 Aug 2026 12:37:04 +0800 Subject: [PATCH] Match the path base and normalize OpenIddict opt-in endpoint paths - Rename completedOnResponseStarting and fix response-start completion comments and docs --- .../Uow/AbpAspNetCoreUnitOfWorkOptions.cs | 2 +- .../AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs | 17 ++++++++++++----- .../OpenIddict/AbpOpenIddictAspNetCoreModule.cs | 14 +++++++++----- ...OpenIddictTokenEndpoint_Integration_Tests.cs | 2 +- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpAspNetCoreUnitOfWorkOptions.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpAspNetCoreUnitOfWorkOptions.cs index 67bfbfaec5..3784cc9005 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpAspNetCoreUnitOfWorkOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpAspNetCoreUnitOfWorkOptions.cs @@ -33,7 +33,7 @@ public class AbpAspNetCoreUnitOfWorkOptions /// even when it is globally disabled (for example /// "/connect" matches "/connect/token" but not "/connections"). A trailing slash is normalized; blank, /// non-absolute, and root ("/") entries are ignored - use - /// to enable it for every request. + /// to enable it for every request handled by the middleware. /// public List CompleteUnitOfWorkOnResponseStartingUrls { get; } = new List(); } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs index 36c00208f9..211a5be2cf 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs @@ -37,7 +37,7 @@ public class AbpUnitOfWorkMiddleware : AbpMiddlewareBase, ITransientDependency using (var uow = _unitOfWorkManager.Reserve(UnitOfWork.UnitOfWorkReservationName)) { - var completedOnResponseStarting = false; + var completionAttemptedOnResponseStarting = false; if (!context.Response.HasStarted && ShouldCompleteOnResponseStarting(context)) { @@ -47,7 +47,7 @@ public class AbpUnitOfWorkMiddleware : AbpMiddlewareBase, ITransientDependency if (_unitOfWorkManager.Current == uow) { // Set before completing so a post-commit failure isn't masked by the completion below. - completedOnResponseStarting = true; + completionAttemptedOnResponseStarting = true; await uow.CompleteAsync(_cancellationTokenProvider.Token); } }); @@ -55,7 +55,7 @@ public class AbpUnitOfWorkMiddleware : AbpMiddlewareBase, ITransientDependency await next(context); - if (!completedOnResponseStarting) + if (!completionAttemptedOnResponseStarting) { await uow.CompleteAsync(_cancellationTokenProvider.Token); } @@ -84,8 +84,15 @@ public class AbpUnitOfWorkMiddleware : AbpMiddlewareBase, ITransientDependency // Normalize a trailing slash ("/connect/" behaves like "/connect") and ignore non-absolute entries. var prefix = url.TrimEnd('/'); - if (prefix.StartsWith("/", StringComparison.Ordinal) && - context.Request.Path.StartsWithSegments(prefix, StringComparison.OrdinalIgnoreCase)) + if (!prefix.StartsWith("/", StringComparison.Ordinal)) + { + continue; + } + + // Match both the request path and the path base + path, so an absolute endpoint that includes + // the path base still matches when the path base is stripped from Request.Path. + if (context.Request.Path.StartsWithSegments(prefix, StringComparison.OrdinalIgnoreCase) || + context.Request.PathBase.Add(context.Request.Path).StartsWithSegments(prefix, StringComparison.OrdinalIgnoreCase)) { return true; } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs index 0826f7a3ae..09f1001104 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.Razor; @@ -50,7 +51,7 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule options.RemoveClientIdClaim(); }); - // Commit tokens/authorizations/sessions written during sign-in before the response is flushed. + // Complete data written while processing OpenIddict requests before the response starts. // Derived from the configured OpenIddict server endpoint paths (including the device endpoint). context.Services.AddOptions() .Configure>((uowOptions, serverOptions) => @@ -65,6 +66,8 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule }); } + private static readonly Uri RootUri = new Uri("http://localhost/"); + private static IEnumerable GetServerEndpointPaths(OpenIddictServerOptions serverOptions) { var endpoints = serverOptions.TokenEndpointUris @@ -77,10 +80,11 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule foreach (var uri in endpoints) { - var path = uri.IsAbsoluteUri ? uri.AbsolutePath : uri.OriginalString; - if (!string.IsNullOrWhiteSpace(path)) + // Resolve relative endpoint URIs (e.g. "connect/token" or "./connect/token") to an absolute path. + var path = (uri.IsAbsoluteUri ? uri : new Uri(RootUri, uri)).AbsolutePath; + if (!string.IsNullOrWhiteSpace(path) && path != "/") { - yield return "/" + path.TrimStart('/'); + yield return path; } } } diff --git a/modules/openiddict/test/Volo.Abp.OpenIddict.AspNetCore.Tests/Volo/Abp/OpenIddict/Integration/OpenIddictTokenEndpoint_Integration_Tests.cs b/modules/openiddict/test/Volo.Abp.OpenIddict.AspNetCore.Tests/Volo/Abp/OpenIddict/Integration/OpenIddictTokenEndpoint_Integration_Tests.cs index cf11cffdf3..483e64694b 100644 --- a/modules/openiddict/test/Volo.Abp.OpenIddict.AspNetCore.Tests/Volo/Abp/OpenIddict/Integration/OpenIddictTokenEndpoint_Integration_Tests.cs +++ b/modules/openiddict/test/Volo.Abp.OpenIddict.AspNetCore.Tests/Volo/Abp/OpenIddict/Integration/OpenIddictTokenEndpoint_Integration_Tests.cs @@ -34,7 +34,7 @@ public class OpenIddictTokenEndpoint_Integration_Tests : AbpWebApplicationFactor [Fact] public async Task Token_Row_Is_Committed_Before_The_Connect_Token_Response_Is_Sent() { - // The OpenIddict module opts "/connect" in by default. + // The OpenIddict module opts its endpoint paths (including "/connect/token") in by default. var response = await RequestTokenAsync(); response.StatusCode.ShouldBe(HttpStatusCode.OK);