Защита SPA сервером авторизации перед первой загрузкой - PullRequest
0 голосов
/ 08 мая 2018

Я использую «новые» шаблоны проектов для приложений Angular SPA в dotnet core 2.1, как написано в статье Использование шаблона проекта Angular с ASP.NET Core .

Но в этой статье ничего не говорится о защите самого SPA. Вся информация, которую я нахожу, касается защиты WEBAPI, но прежде всего я заинтересован в защите SPA.

Это означает: когда я открываю свой SPA, например, https://localhost:44329/ я хотел бы быть перенаправлен на сервер авторизации немедленно вместо нажатия какой-либо кнопки, которая будет выполнять аутентификацию.

Фон:

  • Я должен убедиться, что только аутентифицированные пользователи могут видеть SPA.
  • Я хочу использовать Предоставление кода авторизации для получения токенов обновления с моего сервера авторизации.
  • Я не могу использовать Неявное предоставление , так как токены обновления не могут быть конфиденциальными в браузере

Текущий подход заключается в применении политики MVC, которая требует аутентифицированного пользователя. Но это может быть применено только к контроллеру MVC. Вот почему я добавил HomeController для обслуживания первого запроса.

См. Структуру проекта:

enter image description here

Мой 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. После успешной аутентификации я вижу Index метод домашнего контроллера, но не мой SPA.

Итак, вопрос в том, как я должен обслуживать свой SPA после перенаправления с сервера аутентификации?

Ответы [ 4 ]

0 голосов
/ 13 августа 2018

Использование промежуточного программного обеспечения @ George потребует аутентификации по всем запросам. Если вы хотите запустить это только для localhost, добавьте его в UseSpa, завернутый в блок env.IsDevelopment ().

Еще один вариант, который также хорошо работает в развернутых средах, - это возврат 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-адресу запроса браузера (например, файл cookie со значением Path), вы можете создать для него шаблон с помощью регулярного выражения (или использовать представление бритвы, как в других примерах).

    [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");
        }
    }
0 голосов
/ 18 июня 2018

Внесите это в свой startup.cs:

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

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

Затем поместите ссылку на угловое приложение в index.cshtml:

<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.

0 голосов
/ 01 августа 2018

Кажется, что нет РЕАЛЬНОГО решения, если говорить о SPA.

Чтобы выполнить некоторую логику в SPA, SPA должен быть первоначально загружен.

Но есть некоторая хитрость: в RouterModule вы можете предотвратить начальную навигацию, как показано:

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
    }
  }
}
0 голосов
/ 06 июня 2018

У меня есть кое-что, что, кажется, работает.

В своих исследованиях я наткнулся на этот пост , предлагающий использовать промежуточное ПО вместо атрибута Authorize.

Теперь метод, используемый в этом посте authService, похоже, не работает в моем случае (не знаю, почему, я продолжу расследование и пост, что я найду позже).

Поэтому я решил пойти с более простым решением. Вот мой конфиг

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

В этом случае oidc запускается ДО приложения Spa, и поток работает правильно. Контроллер вообще не нужен.

НТН

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...