在首次加载之前通过授权服务器保护SPA

Dan*_*iel 20 c# .net-core asp.net-core angular

我正在使用dotnet core 2.1中的角度SPA应用程序的"新"项目模板,如文章中所述使用Angular项目模板和ASP.NET Core.

但是这篇文章没有提到保护SPA本身的任何内容.我找到的所有信息都是关于保护WEBAPI,但首先我对保护SPA感兴趣.

这意味着:当我打开我的SPA时,例如https:// localhost:44329 /我希望立即重定向到授权服务器,而不是单击某个将执行身份验证的按钮.

背景:

  • 我必须确保只有经过身份验证的用户才能看到SPA.
  • 我想使用授权代码授权从我的授权服务器获取刷新令牌.
  • 我不能使用隐式授权,因为刷新令牌不能在浏览器上保持私有

当前的方法是实施需要经过身份验证的用户的MVC策略.但这只能应用于MVC控制器.这就是我添加HomeController以满足第一个请求的原因.

见项目结构:

在此输入图像描述

我的Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = "CustomScheme";
        })
        .AddCookie()
        .AddOAuth("CustomScheme", options =>
        {
            // Removed for brevity
        });

    services.AddMvc(config =>
    {
        // Require a authenticated user
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        config.Filters.Add(new AuthorizeFilter(policy));
    });

    // In production, the Angular files will be served from this directory
    services.AddSpaStaticFiles(configuration =>
    {
        configuration.RootPath = "ClientApp/dist";
    });
}

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

    app.UseAuthentication();

    app.UseStaticFiles();
    app.UseSpaStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });

    app.UseSpa(spa =>
    {
        spa.Options.SourcePath = "ClientApp";

        if (env.IsDevelopment())
        {
            spa.UseAngularCliServer(npmScript: "start");
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

当前行为:当我启动我的SPA时,由于MVC策略,我立即被重定向到我的授权服务器.验证成功后,我看到家庭控制器的索引方法,但不是我的SPA.

所以问题是我从认证服务器重定向后应该如何服务我的SPA?

Geo*_*ros 16

我有一些似乎有用的东西.

在我的研究中,我发现这篇文章建议使用中间件而不是Authorize属性.

现在,在post autService中使用的方法在我的情况下似乎不起作用(不知道为什么,我将继续调查并发布我后来发现的whaterver).

所以我决定采用更简单的解决方案.这是我的配置

        app.Use(async (context, next) =>
        {
            if (!context.User.Identity.IsAuthenticated)
            {
                await context.ChallengeAsync("oidc");
            }
            else
            {
                await next();
            }
        });
Run Code Online (Sandbox Code Playgroud)

在这种情况下,oidc在Spa应用程序之前启动并且流程正常工作.根本不需要控制器.

HTH


Cir*_*rem 7

使用@George的中间件将要求对所有请求进行身份验证。如果只想对本地主机运行此命令,请将其添加到包装在env.IsDevelopment()块中的UseSpa下。

对于已部署的环境也很有效的另一个选项是从spa备用路由中返回index.html。

启动:

        if (!env.IsDevelopment())
        {
            builder.UseMvc(routes =>
            {
                routes.MapSpaFallbackRoute(
                    name: "spa-fallback",
                    defaults: new { controller = "Home", action = "AuthorizedSpaFallBack" });
            });
        }
Run Code Online (Sandbox Code Playgroud)

家用控制器:

[Authorize]
public IActionResult AuthorizedSpaFallBack()
{
    var file = _env.ContentRootFileProvider.GetFileInfo("ClientApp/dist/index.html");
    return PhysicalFile(file.PhysicalPath, "text/html");
}
Run Code Online (Sandbox Code Playgroud)

如果您需要base.href来匹配浏览器请求url(例如,具有Path值的cookie),则可以使用正则表达式对其进行模板化(或像其他示例一样使用razer视图)。

    [Authorize]
    public IActionResult SpaFallback()
    {
        var fileInfo = _env.ContentRootFileProvider.GetFileInfo("ClientApp/dist/index.html");
        using (var reader = new StreamReader(fileInfo.CreateReadStream()))
        {
            var fileContent = reader.ReadToEnd();
            var basePath = !string.IsNullOrWhiteSpace(Url.Content("~")) ? Url.Content("~") + "/" : "/";

            //Note: basePath needs to match request path, because cookie.path is case sensitive
            fileContent = Regex.Replace(fileContent, "<base.*", $"<base href=\"{basePath}\">");
            return Content(fileContent, "text/html");
        }
    }
Run Code Online (Sandbox Code Playgroud)


Rob*_*Rob 5

基于 Georges Legros,我设法使用 Identity Server 4(开箱即用的 VS 项目)使其适用于 .Net Core 3,以便在用户未通过身份验证时不会命中 app.UseSpa 管道首先通过身份服务器。这更好,因为您不必等待 SPA 加载然后重定向到登录。

您必须确保授权/角色正常工作,否则 User.Identity.IsAuthenticated 将始终为假。

public void ConfigureServices(IServiceCollection services)
{
    ...

    //Change the following pre-fab lines from

    //services.AddDefaultIdentity<ApplicationUser>()
    //    .AddEntityFrameworkStores<ApplicationDbContext>();

    //To

    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddRoles<IdentityRole>()
            //You might not need the following two settings
            .AddDefaultUI()
            .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddIdentityServer()
            .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

    ...
}
Run Code Online (Sandbox Code Playgroud)

然后添加如下设置如下管道:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller}/{action=Index}/{id?}");
    });

    //Added this to redirect to Identity Server auth prior to loading SPA    
    app.Use(async (context, next) =>
    {
        if (!context.User.Identity.IsAuthenticated)
        {
            await context.ChallengeAsync("Identity.Application");
        }
        else
        {
            await next();
        }
    });

    app.UseSpa(spa =>
    {
        spa.Options.SourcePath = "ClientApp";

        if (env.IsDevelopment())
        {
            spa.UseAngularCliServer(npmScript: "start");
        }
    });
} 
Run Code Online (Sandbox Code Playgroud)