使用 .NET Core 3.1 框架,我尝试使用以下设置配置 Web 平台:
- Razor Pages 应用程序,充当平台的登陆页面,具有平台广告、cookie 同意、隐私政策、联系人以及身份附带的页面(例如登录、注册、管理帐户)等功能/页面。
- Razor Pages 应用程序的身份验证以标准身份方式执行。
- Angular SPA,只有在用户登录后才能访问。
- 使用 Identity Server 进行 OIDC 配置,以便向 Angular SPA 添加身份验证和授权。
所有这三个组件(Razor Pages + Angular + Identity Server)都捆绑到一个 .NET Core Web 项目中。我还搭建了 Identity 框架,以便能够自定义页面的外观和行为。
我几乎能够按照我想要的方式配置应用程序,方法是基本上混合 Razor Pages 选项的启动模板代码(用户帐户存储在本地)和 Angular 模板选项(用户帐户存储在本地)以及一些尝试、错误和调查。
我的申请目前的状态是:
- 用户登录 Razor Pages 应用程序。
- 登录成功,导航栏显示邮件。
- 当我们导航到 SPA 时,我的 Angular 应用程序尝试静默登录并成功:
本地主机:5001/Dashboard(Angular SPA 主路由)
- 如果我们导航到 Razor Pages 应用程序中没有
/Identity
路由(仅用于带有身份的页面)cookie 似乎不再包含正确的信息,并且我在这些路由中没有会话。这意味着,例如,如果我使用SignInManager.IsSignedIn(User)
仅显示受保护的管理页面的导航选项options.Conventions.AuthorizePage($"/Administration")
,如果我在一个有Identity路由的URL中,导航选项卡就会显示,否则不会显示:
本地主机:5001/身份/帐户/登录
本地主机:5001(Razor Pages 应用程序主路由)
- 但是,即使当我位于具有以下内容的 URL 时,也会显示“管理”导航选项卡
/Identity
路线,如果我点击它,我会收到 401 未经授权的错误,因为管理页面前面没有/Identity
route:
本地主机:5001/管理
我已经设法将问题追溯到AddIdentityServerJwt()
。如果没有这个,Razor Pages 应用程序的登录将按预期工作,但我显然无法在之后使用 Angular 应用程序进行身份验证和授权。
我去检查了源代码对于该方法,事实证明它创建了一个新的IdentityServerJwtPolicySchemeForwardSelector
将 JWT 策略方案转发给DefaultIdentityUIPathPrefix
正如您可能已经猜到的,其中仅包含值"/Identity"
.
我通过以下方式配置了我的 Startup 类:
启动.cs
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services
.AddDbContext<ApplicationDbContext>(optionsBuilder =>
{
DatabaseProviderFactory
.CreateDatabaseProvider(configuration, optionsBuilder);
});
services
.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services
.AddIdentityServer()
.AddApiAuthorization<IdentityUser, ApplicationDbContext>();
services
.AddAuthentication()
.AddIdentityServerJwt();
services
.AddControllersWithViews();
services
.AddRazorPages()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage($"/Administration");
});
services
.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddTransient<IEmailSender, EmailSenderService>();
services.Configure<AuthMessageSenderOptions>(configuration);
services.AddTransient<IProfileService, ProfileService>();
}
public void Configure(IApplicationBuilder applicationBuilder, IWebHostEnvironment webHostEnvironment)
{
SeedData.SeedDatabase(applicationBuilder, configuration);
if (webHostEnvironment.IsDevelopment())
{
applicationBuilder.UseDeveloperExceptionPage();
applicationBuilder.UseDatabaseErrorPage();
}
else
{
applicationBuilder.UseExceptionHandler("/Error");
applicationBuilder.UseHsts();
}
applicationBuilder.UseHttpsRedirection();
applicationBuilder.UseStaticFiles();
applicationBuilder.UseCookiePolicy();
if (!webHostEnvironment.IsDevelopment())
{
applicationBuilder.UseSpaStaticFiles();
}
applicationBuilder.UseRouting();
applicationBuilder.UseAuthentication();
applicationBuilder.UseIdentityServer();
applicationBuilder.UseAuthorization();
applicationBuilder.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
applicationBuilder.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (webHostEnvironment.IsDevelopment())
{
if (bool.Parse(configuration["DevelopmentConfigurations:UseProxyToSpaDevelopmentServer"]))
{
spa.UseProxyToSpaDevelopmentServer(configuration["DevelopmentConfigurations:ProxyToSpaDevelopmentServerAddress"]);
}
else
{
spa.UseAngularCliServer(npmScript: configuration["DevelopmentConfigurations:AngularCliServerNpmScript"]);
}
}
});
}
如何配置我的应用程序,以便会话在整个应用程序中可用,而不仅仅是在具有“/Identity”路由的 URL 上可用,同时维护 Razor Pages 应用程序和 Angular 应用程序的身份验证和授权?