我正在使用dotnet core 2.1中角度SPA应用程序的“新”项目模板,如文章Use the Angular project template with ASP.NET Core中所述。
但是这篇文章没有提到保护SPA本身的任何内容。 我找到的所有信息都是关于保护WEBAPI,但首先我对保护SPA感兴趣。
这意味着:当我打开我的SPA时,例如https://localhost:44329/我希望重定向到授权服务器 immediatly ,而不是点击一些将进行身份验证的按钮。
背景:
当前方法是强制执行需要经过身份验证的用户的MVC策略。但这只能应用于MVC控制器。这就是我添加 HomeController 来提供第一个请求的原因。
参见项目结构:
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?
答案 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
的策略检查。