如何在 .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"
});
}`
我正在使用连接到 .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"
});
}`