Я использую «новые» шаблоны проектов для приложений angular SPA в dotnet core 2.1, как написано в статье Используйте шаблон проекта Angular с ASP.NET Core.
Но в этой статье ничего не говорится о защите самого SPA. Вся информация, которую я нахожу, касается защиты WEBAPI, но в первую очередь меня интересует защита SPA.
Это означает: когда я открываю свой СПА, например, https: // localhost: 44329 / Я хотел бы, чтобы меня перенаправили на сервер авторизации немедленно вместо нажатия какой-либо кнопки, которая выполнит аутентификацию.
Задний план:
Текущий подход предназначен для обеспечения соблюдения политики 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");
}
});
}
Текущее поведение: Когда я запускаю свой SPA, я сразу перенаправляюсь на мой сервер авторизации из-за политики MVC. После успешной аутентификации я вижу метод Индекс домашнего контроллера, но не мой SPA.
Итак, вопрос в том, как мне обслуживать свой SPA после того, как я был перенаправлен с сервера аутентификации?
@john да, я так думаю
Вы нашли решение этой проблемы? Сейчас я сталкиваюсь с тем же самым ...
Еще нет. Но на следующей неделе должно быть Решение
Хорошо, тогда я продолжу свои исследования. Я выложу здесь все, что найду, если мне что-нибудь удастся
@ Дэниел, ты понял, что нужно делать? У меня такая же проблема, но мне кажется, что она не работает
@ goldsmit409 добавил текущий ответ. Я спросил об этом эксперта по разработке Google. И то, что я написал, было его предложением.





У меня есть кое-что, что вроде работает.
В своих исследованиях я наткнулся на apon эта почта, предлагающий использовать промежуточное ПО вместо атрибута Authorize.
Теперь метод, использованный в этом посте authService, похоже, не работает в моем случае (не знаю почему, я продолжу расследование и опубликую все, что найду позже).
Поэтому я решил пойти с более простым решением. Вот мой конфиг
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync("oidc");
}
else
{
await next();
}
});
В этом случае oidc запускается ДО приложения Spa, и поток работает правильно. Нет необходимости в контроллере.
HTH
Для Azure также необходимо отфильтровать URL-адрес входа, чтобы избежать бесконечного цикла входа, и настроить заголовки CORS, чтобы браузеры могли работать в междоменном режиме: app.UseCors(policy => policy.SetIsOriginAllowed(origin => origin == "https://login.microsoftonline.com")); 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(); } });
У меня тот же вопрос, но я использую aspnet core 3.0, вот мой вопрос: stackoverflow.com/questions/58363393/…
Внесите это изменение в свой startup.cs:
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
spa.Options.DefaultPage = "/home/index";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
Затем поместите ссылку на приложение angular в 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 auth.
Вы когда-нибудь задумывались, как ссылаться на файлы bundle.js в производственной сборке? Кажется, что SPA выводит их в формате main.[random string].bundle.js при производственной сборке, поэтому статическая ссылка у вас прерывается.
Мы сделали, но это не идеальное решение. Вы можете использовать переменные среды, чтобы определить, какие версии загружать, например : <environment names = "Development"> <script type = "text/javascript" src = "~/runtime.js" asp-append-version = "true"></script> </environment> <environment names = "Staging,Production"> <script type = "text/javascript" asp-src-include = "~/dist/runtime.*.js" asp-append-version = "true"></script> </environment>. Однако будьте осторожны, если вы не удаляете старые файлы, * загрузит все файлы, соответствующие шаблону, что вызовет конфликты.
Похоже, что нет НАСТОЯЩЕГО решения, когда речь идет о 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
}
}
}
Глядя на другие комментарии, определенно есть другие варианты
Использование промежуточного программного обеспечения @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");
}
}
Он работает в продакшене и помогает мне установить базовый URL. Спасибо
Как инициализировать переменную _env?
@Tal Вставить IHostingEnvironment в конструктор и присвоить переменной класса только для чтения.
Промежуточное ПО @George не будет требовать аутентификации для всех запросов, если вы поместите его прямо над вызовом app.UseSpa (после UseMvc / UseEndpoit)
На основе Georges Legros мне удалось заставить это работать для .Net Core 3 с Identity Server 4 (готовый проект VS), так что конвейер 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>();
...
}
Затем добавьте следующую настройку следующего канала:
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");
}
});
}
У меня тот же вопрос, но я использую aspnet core 3.0, вот мой вопрос: stackoverflow.com/questions/58363393/…
@AhmerAliAhsan мое решение для aspnet core 3.0 preview-8
@bob, не могли бы вы проверить мой вопрос. Может ты сможешь мне помочь
@AhmerAliAhsan, вы не включаете часть Services.AddIdentity в решение, которое я предложил выше (это мое предположение) - вам определенно нужно иметь .AddRoles <IdentityRole> () из памяти, иначе context.User.Identity.IsAuthenticated всегда будет ложным
Для лазурного объявления (Жорж отвечает с karmas edit без корса):
в ConfigureServices (сервисы IServiceCollection):
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(_configuration.GetSection("AzureAd"));
в Configure (приложение IApplicationBuilder, 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();
});
Другими словами, вы хотите контролировать доступ к HTML и JavaScript?