在首次加载

时间:2018-05-08 04:34:54

标签: c# angular asp.net-core .net-core

我正在使用dotnet core 2.1中角度SPA应用程序的“新”项目模板,如文章Use the Angular project template with ASP.NET Core中所述。

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

这意味着:当我打开我的SPA时,例如https://localhost:44329/我希望重定向到授权服务器 immediatly ,而不是点击一些将进行身份验证的按钮。

背景:

  • 我必须确保只允许经过身份验证的用户查看SPA。
  • 我想使用授权代码授予从我的授权服务器获取刷新令牌。
  • 我无法使用隐式授权,因为刷新令牌无法在浏览器中保持私密状态

当前方法是强制执行需要经过身份验证的用户的MVC策略。但这只能应用于MVC控制器。这就是我添加 HomeController 来提供第一个请求的原因。

参见项目结构:

enter image description here

My 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");
        }
    });
}

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

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

7 个答案:

答案 0 :(得分:12)

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

在我的研究中,我发现了this question建议使用中间件而不是授权属性。

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

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

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

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

HTH

答案 1 :(得分:4)

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

另一个适用于已部署环境的选项是从spa后备路径返回index.html。

启动:

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

HomeController:

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

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

    [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");
        }
    }

答案 2 :(得分:3)

对startup.cs进行此更改:

app.UseSpa(spa =>
{
    spa.Options.SourcePath = "ClientApp";
    spa.Options.DefaultPage = "/home/index";

    if (env.IsDevelopment())
    {
        spa.UseAngularCliServer(npmScript: "start");
    }
});

然后将引用放在index.cshtml中的angular应用程序:

<app-root></app-root>

并确保在index.cshtml文件或布局中包含所有需要的文件:

<link href="~/styles.bundle.css" rel="stylesheet" />

<script type="text/javascript" src="~/inline.bundle.js" asp-append-version="true"></script>
<script type="text/javascript" src="~/polyfills.bundle.js" asp-append-version="true"></script>
<script type="text/javascript" src="~/vendor.bundle.js" asp-append-version="true"></script>
<script type="text/javascript" src="~/main.bundle.js" asp-append-version="true"></script>

我们仍在解决所有引用包的问题,​​但这将使基本的SPA在asp.net auth后面运行。

答案 3 :(得分:1)

基于Georges Legros,我设法使它与Identity Server 4(现成的VS项目)一起在.Net Core 3上运行,因此如果用户使用,则不会点击app.UseSpa管道首先不通过身份服务器进行身份验证。这样更好,因为您不必等待SPA加载才可以重定向到登录名。

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

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>();

    ...
}

然后添加以下内容以设置以下管道:

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");
        }
    });
} 

答案 4 :(得分:0)

谈到SPA时似乎没有REAL解决方案。

为了在SPA中执行某些逻辑,必须首先加载SPA。

但是有一些技巧:在RouterModule中,可以防止显示所示的initialNavigation:

const routes: Routes = [
  {
    path: '',
    redirectTo: 'about',
    pathMatch: 'full'
  },
  {
    path: '**',
    redirectTo: 'about'
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { initialNavigation: false })],
  exports: [RouterModule]
})
export class AppRoutingModule {}

然后在您app.component.ts中,您需要注意身份验证:

@Component({
  selector: 'flight-app',
  templateUrl: './app.component.html'
})
export class AppComponent {
  constructor(private router: Router, private oauthService: OAuthService) {
    if (this.oauthService.isAuthenticated()) {
      this.router.navigate(['/home']);
    } else {
      // login Logic
    }
  }
}

答案 5 :(得分:0)

对于 azure 广告(乔治回答,没有 cors 的 karmas 编辑):

在配置服务(IServiceCollection 服务)中:

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApp(_configuration.GetSection("AzureAd"));

在配置中(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory):

app.UseAuthentication();

app.Use(async (context, next) =>
{
    if (!context.User.Identity.IsAuthenticated && context.Request.Path != "/signin-oidc")
    {
        await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme);
    }
    else
    {
        await next();
    }
});

app.UseEndpoints(endpoints =>
{
    var builder = endpoints.MapControllers();
    builder.RequireAuthorization();
});
        

答案 6 :(得分:0)

其他答案很好,但它们不会验证您的政策,它们只会验证您是否通过了身份验证。

一种可能的解决方案是使用 IMiddelware 工厂:

services.AddAuthorization(options =>
{
    options.AddPolicy("Tenant", policy => policy.RequireAuthenticatedUser().Requirements.Add(new TenantRequirement()));
});
app.UseMiddleware<TenantAuthorizationMiddleware>();
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;

namespace FieldCap.Service.Middleware
{
    public class TenantAuthorizationMiddleware : IMiddleware
    {
        private readonly IAuthorizationService _authorizationService;

        public TenantAuthorizationMiddleware(IAuthorizationService authorizationService)
        {
            _authorizationService = authorizationService;
        }

        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            var result = await _authorizationService.AuthorizeAsync(context.User, "Tenant");

            if (!result.Succeeded)
            {
                context.Response.Redirect("/login");

                return;
            }

            await next(context);
        }
    }
}

这将确保每个请求也通过 tenant 的策略检查。