使用 .AddIdentityServerJwt() 时,.NET Core Razor Pages 应用程序的身份验证不适用于没有“/Identity”路由的视图
Authentication for .NET Core Razor Pages application doesn't work for views without an "/Identity" route while using .AddIdentityServerJwt()
使用 .NET Core 3.1 框架,我正在尝试使用以下设置配置 Web 平台:
- 一个 Razor Pages 应用程序,作为平台的登录页面 features/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 应用程序尝试静默登录并成功:
localhost:5001/Dashboard (Angular SPA home route)
- 如果我们导航到没有
/Identity
路由的 Razor Pages 应用程序的一部分(仅用于带有 Identity 的页面),cookie 似乎不再包含正确的信息我在这些路线上没有会话。这意味着,例如,如果我使用 SignInManager.IsSignedIn(User)
仅显示受 options.Conventions.AuthorizePage($"/Administration")
保护的管理页面的导航选项,如果我在具有 URL 的 URL 中Identity路由,会显示导航标签,否则不显示:
localhost:5001/Identity/Account/Login
localhost:5001(Razor Pages 应用程序主路径)
- 但是,即使当我在具有
/Identity
路由的 URL 上时显示管理导航选项卡,如果我单击它,我将收到 401 未经授权的错误,因为管理页面前面没有 /Identity
路由:
localhost:5001/管理
我已经设法将问题追溯到 AddIdentityServerJwt()
。如果没有这个,Razor Pages 应用程序的登录将按预期工作,但我显然无法在之后对 Angular 应用程序使用身份验证和授权。
我去检查了 source code 的那个方法,结果发现它创建了一个新的 IdentityServerJwtPolicySchemeForwardSelector
将 JWT 策略方案转发给 DefaultIdentityUIPathPrefix
,正如你可能猜对了,只包含值"/Identity"
.
我已按以下方式配置我的启动 class:
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”路由的 URLs 上可用,同时保持身份验证和授权对于 Razor Pages 应用程序和 Angular 应用程序?
我遇到了同样的问题并通过添加我自己的 PolicyScheme 来解决它,它根据请求路径决定应该使用哪种类型的身份验证。我所有的剃刀页面都有一个以“/Identity”或“/Server”开头的路径,所有其他请求都应该使用 JWT。
我使用以下代码在 ConfigureServices 中进行设置:
// Add authentication using JWT and add a policy scheme to decide which type of authentication should be used
services.AddAuthentication()
.AddIdentityServerJwt()
.AddPolicyScheme("ApplicationDefinedAuthentication", null, options =>
{
options.ForwardDefaultSelector = (context) =>
{
if (context.Request.Path.StartsWithSegments(new PathString("/Identity"), StringComparison.OrdinalIgnoreCase) ||
context.Request.Path.StartsWithSegments(new PathString("/Server"), StringComparison.OrdinalIgnoreCase))
return IdentityConstants.ApplicationScheme;
else
return IdentityServerJwtConstants.IdentityServerJwtBearerScheme;
};
});
// Use own policy scheme instead of default policy scheme that was set in method AddIdentityServerJwt
services.Configure<AuthenticationOptions>(options => options.DefaultScheme = "ApplicationDefinedAuthentication");
使用 .NET Core 3.1 框架,我正在尝试使用以下设置配置 Web 平台:
- 一个 Razor Pages 应用程序,作为平台的登录页面 features/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 应用程序尝试静默登录并成功:
localhost:5001/Dashboard (Angular SPA home route)
- 如果我们导航到没有
/Identity
路由的 Razor Pages 应用程序的一部分(仅用于带有 Identity 的页面),cookie 似乎不再包含正确的信息我在这些路线上没有会话。这意味着,例如,如果我使用SignInManager.IsSignedIn(User)
仅显示受options.Conventions.AuthorizePage($"/Administration")
保护的管理页面的导航选项,如果我在具有 URL 的 URL 中Identity路由,会显示导航标签,否则不显示:
localhost:5001/Identity/Account/Login
localhost:5001(Razor Pages 应用程序主路径)
- 但是,即使当我在具有
/Identity
路由的 URL 上时显示管理导航选项卡,如果我单击它,我将收到 401 未经授权的错误,因为管理页面前面没有/Identity
路由:
localhost:5001/管理
我已经设法将问题追溯到 AddIdentityServerJwt()
。如果没有这个,Razor Pages 应用程序的登录将按预期工作,但我显然无法在之后对 Angular 应用程序使用身份验证和授权。
我去检查了 source code 的那个方法,结果发现它创建了一个新的 IdentityServerJwtPolicySchemeForwardSelector
将 JWT 策略方案转发给 DefaultIdentityUIPathPrefix
,正如你可能猜对了,只包含值"/Identity"
.
我已按以下方式配置我的启动 class:
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”路由的 URLs 上可用,同时保持身份验证和授权对于 Razor Pages 应用程序和 Angular 应用程序?
我遇到了同样的问题并通过添加我自己的 PolicyScheme 来解决它,它根据请求路径决定应该使用哪种类型的身份验证。我所有的剃刀页面都有一个以“/Identity”或“/Server”开头的路径,所有其他请求都应该使用 JWT。
我使用以下代码在 ConfigureServices 中进行设置:
// Add authentication using JWT and add a policy scheme to decide which type of authentication should be used
services.AddAuthentication()
.AddIdentityServerJwt()
.AddPolicyScheme("ApplicationDefinedAuthentication", null, options =>
{
options.ForwardDefaultSelector = (context) =>
{
if (context.Request.Path.StartsWithSegments(new PathString("/Identity"), StringComparison.OrdinalIgnoreCase) ||
context.Request.Path.StartsWithSegments(new PathString("/Server"), StringComparison.OrdinalIgnoreCase))
return IdentityConstants.ApplicationScheme;
else
return IdentityServerJwtConstants.IdentityServerJwtBearerScheme;
};
});
// Use own policy scheme instead of default policy scheme that was set in method AddIdentityServerJwt
services.Configure<AuthenticationOptions>(options => options.DefaultScheme = "ApplicationDefinedAuthentication");