我在使用 Microsoft Entra 进行身份验证的 .Net 8 Blazor 混合应用程序中遇到策略身份验证问题。
内部身份验证正在工作,我有一个页面可以通过
<AuthorizeView>
验证策略是否正常工作并设置声明。
我的布局页面有一个强制用户登录或发送到 Entra 登录流程的块:
<AuthorizeView Policy="@PolicyConstants.MustBeAuthenticated">
<Authorized>
@Body
</Authorized>
<NotAuthorized>
@{
var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
<RedirectToEntraSignInComponent ReturnUrl="@returnUrl" />
}
</NotAuthorized>
</AuthorizeView>
在我的身份验证模型中,我有两个角色:管理员和客户端。 我为每个用户定义了策略,并且在我的根页面上有这个嵌套的
AuthorizeView
,这样我就可以根据用户的角色转发用户。 这有助于将他们发送到正确的页面。
<CascadingAuthenticationState>
<AuthorizeView Policy="@PolicyConstants.IsAdmin" Context="AdminContext">
<Authorized>
<RedirectToAdminDashboardComponent />
</Authorized>
<NotAuthorized>
<AuthorizeView Policy="@PolicyConstants.IsClient" Context="ClientContext">
<Authorized>
<RedirectToClientDashboardComponent />
</Authorized>
<NotAuthorized>
<RedirectTo403Component />
</NotAuthorized>
</AuthorizeView>
</NotAuthorized>
</AuthorizeView>
</CascadingAuthenticationState>
因此,管理员最终出现在他们的仪表板上。 但是,我需要保护该页面以确保只有管理员可以访问它。
[Authorize(Policy = PolicyConstants.IsAdmin)]
public partial class AdminDashboardPage
当我没有设置授权属性时,它工作正常,但该页面对任何经过身份验证的人开放,无论其角色/策略状态如何。
当我向上面的页面添加 Authorize 属性时,用户总是被转发到此 url,即使他们首先满足访问该页面的策略要求。
/MicrosoftIdentity/帐户/AccessDenied?ReturnUrl=%2Fadmin
我想避免在每个页面上放置授权视图来强制执行策略,并且我也不希望每个策略都必须有一个带有授权视图的布局页面。 我不明白为什么 auth 属性每次都失败。 我确实尝试在客户端和服务器项目中定义策略,但这并没有解决问题。
我针对此问题找到的解决方案是 Microsoft.Identity 和 ASP.NET core 授权根本不能一起使用。
<AuthorizeView>
属性通过我们的 Microsoft.Identity 配置通过策略发挥作用。
[Authorize]
属性正在使用ASP.NET core授权。
当我们进行身份验证时,我们仅通过 Microsoft.Identity 进行身份验证。 由于某种原因,此授权不会跨入 Core,因此授权属性在整页加载时始终会失败。
首先,创建一个中间件来传递.Net core Auth。 如果没有这个,在整个页面加载时,身份验证状态就会丢失。 发生这种情况时,用户在到达
<AuthorizeView>
之前就被踢出了
public class BlazorAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
public Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
{
return next(context);
}
}
然后,在您的 DI 中注册中间件
services.AddSingleton<IAuthorizationMiddlewareResultHandler, BlazorAuthorizationMiddlewareResultHandler>();
要控制由于
[Authorize]
属性而发送未经授权的用户的 URL,您可以添加以下内容:
services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options => {
options.AccessDeniedPath = new PathString("/errors/AccessDenied");
options.ReturnUrlParameter = "returnUrl";
});
我们的授权属性正在发挥作用,因为我们正在使用带有自定义逻辑的策略
public class IsAdminRequirement : AuthorizationHandler<IsAdminRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsAdminRequirement requirement)
{
if (context.User.IsAdmin()) //this is a custom extension method that evaluates claims
{
context.Succeed(requirement);
}
else
{
context.Fail(new AuthorizationFailureReason(this, $"User is not an Admin"));
}
return Task.CompletedTask;
}
}
当你有像上面这样的需求,不需要注入任何资源时,auth框架会自动注册它。如果您需要注入服务,您可以将其分为两个类并通过 DI 注册处理程序:
// NOTE: If a handler implements both IAuthorizationHandler and IAuthorizationRequirement,
// it does not need to be registered due to auto-registration that happens within
// the auth framework. Only register below those handlers that need DI injection
services.AddScoped<IAuthorizationHandler, MyCustomRequirementHandler>();
然后在我们的 DI 中注册授权过滤器
services.AddRazorPages().AddMvcOptions(options =>
{
options.Filters.Add(new AuthorizeFilter(PolicyConstants.IsAdmin));
}).AddMicrosoftIdentityUI();
然后注册策略要求并将其绑定到字符串常量
services.AddAuthorizationBuilder()
.AddPolicy(PolicyConstants.IsAdmin,
policy => policy.AddRequirements(new IsAdminRequirement()))
现在,我们可以使用
AuthorizeView
中的策略来显示/隐藏内容,并通过 [Authorize]
属性来限制访问。