如何在 .NET Core 中为 OpenIddict 端点创建 'prettier' URL

How do I create a 'prettier' URL for OpenIddict endpoints in .NET Core

我正在使用连接到 .NET Core 后端的 angular 前端,并使用 OpenIddict 进行授权。当我登陆登录页面时,url 如下所示:

https://localhost:44340/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%3Fresponse_type%3Dcode%26client_id%3DclientIDExample%26state%3DYUVBdDJvUG04SUpVTzZqSEJvRlMxWFZnWU0xSUVsSi1IVnR31pCMGY1m %26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A4200%26scope%3Dopenid%2520profile%2520email%2520offline_access%26code_challenge%3DAg0TCRqJBaFqpa8sJb--J67Yd88tNPmouGonUvBbBbM%26code_challenge_method%3DS26 %26nonce%3DYUVBdDJvUG04SUpVTzZqSEJvRlMxWFZnWU0xSUVsSi1IVnR1WEY2R3pCMG1m

这是我希望用户看到的 'user friendly' url,而不是授权端点:

https://localhost:44340/Account/Login

这是我正在点击的授权代码的一部分:

    [HttpGet("~/connect/authorize")]
    [HttpPost("~/connect/authorize")]
    [IgnoreAntiforgeryToken]
    public async Task<IActionResult> Authorize()
    {
        var request = HttpContext.GetOpenIddictServerRequest() ??
            throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");

        // Retrieve the user principal stored in the authentication cookie.
        // If it can't be extracted, redirect the user to the login page.
        var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
        if (result is null || !result.Succeeded)
        {
            // If the client application requested promptless authentication,
            // return an error indicating that the user is not logged in.
            if (request.HasPrompt(Prompts.None))
            {
                return Forbid(
                    authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                    properties: new AuthenticationProperties(new Dictionary<string, string>
                    {
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
                        [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in."
                    }));
            }

            return Challenge(
                authenticationSchemes: IdentityConstants.ApplicationScheme,
                properties: new AuthenticationProperties
                {
                    RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
                        Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
                });
        }

}

这是我的 Startup class:

    public class Startup
{
    public Startup(IConfiguration configuration, IHostEnvironment env)
    {
        Configuration = configuration;
        _env = env;
    }

    public IConfiguration Configuration { get; }

    public IHostEnvironment _env { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        var connectionString = Configuration[$"Connections:DefaultConnection"];
        var EncryptionCertificate = new X509Certificate2(Convert.FromBase64String(Configuration["EncCert"]), (string)null, X509KeyStorageFlags.MachineKeySet);
        var SignCertificate = new X509Certificate2(Convert.FromBase64String(Configuration["SigCert"]), (string)null, X509KeyStorageFlags.MachineKeySet);
        services.AddRazorPages();

        //DbContext OnConfiguring gets done here
        services.AppDataContext(connectionString);
        // OpenIddict offers native integration with Quartz.NET to perform scheduled tasks
        // (like pruning orphaned authorizations/tokens from the database) at regular intervals.
        services.AddQuartz(options =>
        {
            options.UseMicrosoftDependencyInjectionJobFactory();
            options.UseSimpleTypeLoader();
            options.UseInMemoryStore();
        });

        // Register the Quartz.NET service and configure it to block shutdown until jobs are complete.
        services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
        services.AddIdentity<ApplicationUserModel, IdentityRole>()
            .AddEntityFrameworkStores<DataContext>()
            .AddDefaultTokenProviders();
        services.Configure<IdentityOptions>(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 = Claims.Name;
            options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
            options.ClaimsIdentity.RoleClaimType = Claims.Role;

            // 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.IdentityServer(EncryptionCertificate, SignCertificate);
        services.AddAuthentication(Configuration);

        //Adds some claim data
        services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUserModel>, AdditionalUserClaimsPrincipalFactory>();


        services.AddCors(options => options.AddPolicy("AllowCors",
                  builder =>
                  {
                      builder.SetIsOriginAllowed(_ => true)
                      .AllowAnyHeader()
                      .AllowAnyMethod()
                      .AllowCredentials();
                  })
          );

        if (_env.IsDevelopment())
        {
            //Script will populate Database but should be scripted for production
            services.AddHostedService<Worker>();
        }
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseCors("AllowCors");


        //Middleware that takes care of authorization and authentication
        //Should always happen before the endpoints
        //These methods allow for decorating Controllers with the Authorize attribute, which controls page and feature access
        app.UseAuthentication();
        app.UseAuthorization();

        //Endpoints instead of Razor pages
        //Because using APIs and Angular
        app.UseEndpoints(endpoints =>
        {
                //endpoints.MapControllers();
            endpoints.MapDefaultControllerRoute();
        });
    }
}

我需要重写吗?重定向?我需要保留端点,但无法向用户显示整个端点。

我认为大多数系统都会向您显示 'ugly' link,如果您尝试使用 Google 或 Facebook 登录,您将看到相同的长 link.

社区中正在进行的工作可能会产生更好的 links,其中之一是 Pushed Authorization Requests (PAR),但我怀疑所有令牌提供商都支持它,我不知道支持是什么今天在 ASP.NET Core 中。

我最终不得不将长端点存储在助手中,这样我就可以传递端点但得到一个漂亮的 ReturnUri,我现在将其传递为 'Home':

新 link 是:

https://localhost:44340/Account/Login?ReturnUrl=Home

    [HttpGet("~/connect/authorize")]
    [HttpPost("~/connect/authorize")]
    [IgnoreAntiforgeryToken]
    public async Task<IActionResult> Authorize()
    {
        var request = HttpContext.GetOpenIddictServerRequest() ??
            throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");


        if (_urlHelper != null)
        {
            _urlHelper.Value.urlEndpoint = Request.PathBase + Request.Path + QueryString.Create(
                        Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList());
        }

        // Retrieve the user principal stored in the authentication cookie.
        // If it can't be extracted, redirect the user to the login page.
        var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
        if (result is null || !result.Succeeded)
        {
            // If the client application requested promptless authentication,
            // return an error indicating that the user is not logged in.
            if (request.HasPrompt(Prompts.None))
            {
                return Forbid(
                    authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                    properties: new AuthenticationProperties(new Dictionary<string, string>
                    {
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
                        [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in."
                    }));
            }

            return Challenge(
                authenticationSchemes: IdentityConstants.ApplicationScheme,
                properties: new AuthenticationProperties
                {
                    RedirectUri = "Home"
                });
        }`