使用 .AddIdentityServerJwt() 时,.NET Core Razor Pages 应用程序的身份验证不适用于没有“/Identity”路由的视图

Joã*_*iva 0 asp.net-identity openid-connect .net-core asp.net-core identityserver4

使用 .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 模板选项(用户帐户存储在本地)以及一些尝试、错误和调查。

我的申请目前的状态是:

  1. 用户登录 Razor Pages 应用程序。
  2. 登录成功,导航栏显示邮件。
  3. 当我们导航到 SPA 时,我的 Angular 应用程序尝试静默登录并成功:

本地主机:5001/Dashboard(Angular SPA 主路由)

在此输入图像描述

  1. 如果我们导航到 Razor Pages 应用程序中没有路由的部分/Identity(仅用于带有身份的页面),则 cookie 似乎不再包含正确的信息,并且我在这些路由中没有会话。这意味着,例如,如果我使用SignInManager.IsSignedIn(User)仅显示受 保护的管理页面的导航选项options.Conventions.AuthorizePage($"/Administration"),如果我位于具有身份路由的 URL 中,则将显示导航选项卡,否则将显示不显示:

本地主机:5001/身份/帐户/登录

在此输入图像描述

localhost:5001(Razor Pages 应用程序主路由)

在此输入图像描述

  1. 但是,即使当我位于具有路由的 URL 时显示“管理”导航选项卡/Identity,如果单击它,我也会收到 401 未经授权的错误,因为“管理”页面前面没有该/Identity路由:

本地主机: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"]);
            }
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

如何配置我的应用程序,以便会话在整个应用程序中可用,而不仅仅是在具有“/Identity”路由的 URL 上可用,同时维护 Razor Pages 应用程序和 Angular 应用程序的身份验证和授权?

Tho*_*ösi 6

我遇到了同样的问题,并通过添加我自己的 PolicyScheme 来解决它,该 PolicyScheme 根据请求路径决定应该使用哪种类型的身份验证。我所有的 razor 页面都有一个以“/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");
Run Code Online (Sandbox Code Playgroud)