新文章 网摘 文章 随笔 日记

ASP.NET Core 6.0 - 基于声明的授权

本文介绍基于声明的授权的实现。我假设您已经下载了Core 6.0 - 设备2FA项目的用户 ASP.NET

使用设备的用户 2FA 项目和文章系列

本系列文章介绍了 ASP.NET 核心 6.0 与 Visual Studio 2022 的实现。ASP.NET 核心 6.0 - 具有设备 2FA 项目 (UWD2FAP) 的用户实现 WebAuthn(也称为 FIDO2),而不是用于双重身份验证 (2FA) 的身份验证器应用。该项目实现引导 v5 和引导本机。用户注册后,他们可以使用 Windows 你好、安卓锁屏或 FIDO2 安全密钥启用 2FA。如果我在创建肯哈格蒂时有这个项目。Com 三年前,我会从这个用户身份验证模板开始。最新版本在预览版中发布。肯哈格蒂。通讯社我鼓励您在“管理帐户>双因素身份验证”中注册和评估多个2FA设备。有关详细信息、屏幕截图和相关文章,请参阅 Core 6.0 - 使用设备 2FA 项目的用户 ASP.NET。详细信息页面包括版本更改日志。

简单来说,身份验证是验证用户是谁的过程,而授权是验证他们有权访问的内容的过程。

将这些流程与实际示例进行比较,当您在机场通过安检时,您需要出示您的身份证件以验证您的身份。然后,当您到达登机口时,您向乘务员出示登机牌,以便他们授权您登机并允许进入飞机。AUTH0 - 什么是身份验证和授权?

在核心或基于声明的访问控制 (CBAC) ASP.NET 基于声明的授权并不容易。虽然 ASP.NET 核心或基于角色的访问控制 (RBAC) 中基于角色的授权可能更易于理解和实现,但 CBAC 通过支持最小特权原则,可以更精细地控制用户可以访问的内容。最小特权的信息安全原则断言,应仅向用户和应用程序授予对执行其工作所需的数据和操作的访问权限。请参阅使用最小特权原则增强安全性。RBAC 是使用预定义的声明类型.角色实现的。CBAC 通过声明标识和授权策略实现。策略表示授权要求的集合以及评估这些要求的一个或多个方案,所有这些方案都必须成功才能使授权成功。

硬部件目录

如何允许具有任何授权声明的用户访问 “/Admin/索引” 页。
如何允许具有管理员声明的用户成功满足所有要求。
如何将管理员与非管理员隔离开来。
如何要求为所有策略启用双重身份验证。
如何允许具有任何授权声明的用户导航到“/Admin/索引”页。
硬质零件的解决方案。

类是基于声明的标识的具体实现。即,由声明集合描述的标识。声明是由颁发者对实体做出的声明,用于描述该实体的属性、权利或其他质量。这种实体被称为索赔的标的。声明由声明类表示。声明标识中包含的声明描述相应标识所表示的实体,并可用于做出授权和身份验证决策。与完全依赖于角色的更传统的访问模型相比,基于声明的访问模型具有许多优势。例如,声明可以提供有关它们所表示的标识的更丰富的信息,并且可以以更具体的方式评估授权或身份验证。声明标识类

UWD2FAP 实现应用用户声明实体以存储应用用户的声明属性。SignInService 中的“创建用户”原则辅助同步方法添加“名称标识符”、“名称”、“Sid”和“身份验证方法”预定义的声明类型,然后为每个应用用户声明添加一个声明。

服务 > 登录服务
/// <summary>
/// Creates a <see cref="ClaimsPrincipal"/> for the <paramref name="appUser"/>, asynchronous.
/// </summary>
/// <param name="appUser">The the user to create a <see cref="ClaimsPrincipal"/> for.</param>
/// <param name="authenticationMethod">Name of the method used to authenticate the user.</param>
/// <returns><see cref="Task{TResult}"/> for <see cref="ClaimsPrincipal"/></returns>
private async Task<ClaimsPrincipal> CreateUserPrincipalAsync(AppUser appUser, string authenticationMethod)
{
    var claims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, appUser.Id.ToString(), ClaimValueTypes.Integer, AppSettings.ClaimIssuer),
                new Claim(ClaimTypes.Name, appUser.LoginName, ClaimValueTypes.String, AppSettings.ClaimIssuer),
                new Claim(ClaimTypes.Sid, appUser.SecurityStamp, ClaimValueTypes.String, AppSettings.ClaimIssuer),
                new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod, ClaimValueTypes.String, AppSettings.ClaimIssuer)
            };

    var appUserClaims = await _userService.GetAppUserClaimsByAppUserIdAsync(appUser.Id).ConfigureAwait(false);
    foreach (var appUserClaim in appUserClaims)
        claims.Add(new Claim(appUserClaim.Type, appUserClaim.Value, appUserClaim.ValueType!, AppSettings.ClaimIssuer));

    return new ClaimsPrincipal(new ClaimsIdentity(claims, AppSettings.ApplicationScheme));
}

The UWD2FAP implements user claims like TwoFactorEnabled, TermsOfService, MustChangePassword, and authorization claims. To manage authorization claims, I developed an AuthorizationClaims class which holds the generic Claim values. The UWD2FAP defines application claims with AppSettings, AppClaimTypes, and AppClaimValues static classes which define constants for the claim.

AuthorizationClaims.cs
// Copyright © Ken Haggerty (https://kenhaggerty.com)
// Licensed under the MIT License.
namespace UsersWithDevice2FA.Models.AppClaims;
/// <summary>
/// Generic readonly list of all <see cref="Claim"/> used for authorization.
/// </summary>
public static class AuthorizationClaims
{
    public static readonly Claim Administrator = new(AppClaimTypes.Super, AppClaimValues.Administrator,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);
    public static readonly Claim Security = new(AppClaimTypes.Super, AppClaimValues.Security,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);

    public static readonly Claim EmailRead = new(AppClaimTypes.Email, AppClaimValues.Read,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);
    public static readonly Claim EmailWrite = new(AppClaimTypes.Email, AppClaimValues.Write,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);
    public static readonly Claim EmailSend = new(AppClaimTypes.Email, AppClaimValues.Send,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);

    public static readonly Claim AppUserRead = new(AppClaimTypes.AppUser, AppClaimValues.Read,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);
    public static readonly Claim AppUserWrite = new(AppClaimTypes.AppUser, AppClaimValues.Write,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);
    public static readonly Claim AppUserDelete = new(AppClaimTypes.AppUser, AppClaimValues.Delete,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);

    public static readonly Claim AuthorizationRead = new(AppClaimTypes.Authorization, 
        AppClaimValues.Read, ClaimValueTypes.String, AppSettings.ClaimIssuer);
    public static readonly Claim AuthorizationAssign = new(AppClaimTypes.Authorization,
        AppClaimValues.Assign, ClaimValueTypes.String, AppSettings.ClaimIssuer);

    public static readonly Claim CredentialRead = new(AppClaimTypes.Credential, AppClaimValues.Read,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);
    public static readonly Claim CredentialWrite = new(AppClaimTypes.Credential, AppClaimValues.Write,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);
    public static readonly Claim CredentialDelete = new(AppClaimTypes.Credential, AppClaimValues.Delete,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);

    public static readonly Claim ChallengeRead = new(AppClaimTypes.Challenge, AppClaimValues.Read,
        ClaimValueTypes.String, AppSettings.ClaimIssuer);
    public staticreadonlyClaimChallengeDelete=new(AppClaimTypes.Challenge,AppClaimValues.Delete,ClaimValueTypes.String,AppSettings.ClaimIssuer);/// <summary>/// Gets an array of all <see cref="AuthorizationClaims"/>./// </summary>/// <returns><see cref="T:Claim[]"/></returns>publicstaticClaim[]ToArray(){Type t =typeof(AuthorizationClaims);FieldInfo[] fields = t.GetFields(BindingFlags.Static|BindingFlags.Public);List<Claim> claims =new();foreach(FieldInfofiin fields){if(fi.GetValue(t)isClaim claim)if(claim.Value!=AppClaimValues.NA)
                    claims.Add(claim);}return claims.ToArray();}/// <summary>/// Gets an array of all <see cref="AuthorizationClaims"/> where/// Type = <see cref="AppClaimTypes.Super"/>./// </summary>/// <returns><see cref="T:Claim[]"/></returns>publicstaticClaim[]ToSuperArray(){Type t =typeof(AuthorizationClaims);FieldInfo[] fields = t.GetFields(BindingFlags.Static|BindingFlags.Public);List<Claim> claims =new();foreach(FieldInfofiin fields){if(fi.GetValue(t)isClaim claim)if(claim.Type==AppClaimTypes.Super)
                    claims.Add(claim);}return claims.ToArray();}/// <summary>/// Gets an array of all <see cref="AuthorizationClaims"/> where/// Type = <see cref="AppClaimTypes.Email"/>./// </summary>/// <returns><see cref="T:Claim[]"/></returns>publicstaticClaim[]ToEmailArray(){Type t =typeof(AuthorizationClaims);FieldInfo[] fields = t.GetFields(BindingFlags.Static|BindingFlags.Public);List<Claim> claims =new();foreach(FieldInfofiin fields){if(fi.GetValue(t)isClaim claim)if(claim.Type==AppClaimTypes.Email)
                    claims.Add(claim);}return claims.ToArray();}/// <summary>/// Gets an array of all <see cref="AuthorizationClaims"/> where/// Type = <see cref="AppClaimTypes.AppUser"/>./// </summary>/// <returns><see cref="T:Claim[]"/></returns>publicstaticClaim[]ToAppUserArray(){Type t =typeof(AuthorizationClaims);FieldInfo[] fields = t.GetFields(BindingFlags.Static|BindingFlags.Public);List<Claim> claims =new();foreach(FieldInfofiin fields){if(fi.GetValue(t)isClaim claim)if(claim.Type==AppClaimTypes.AppUser)
                    claims.Add(claim);}return claims.ToArray();}/// <summary>/// Gets an array of all <see cref="AuthorizationClaims"/> where/// Type = <see cref="AppClaimTypes.Authorization"/>./// </summary>/// <returns><see cref="T:Claim[]"/></returns>publicstaticClaim[]ToAuthorizationArray(){Type t =typeof(AuthorizationClaims);FieldInfo[] fields = t.GetFields(BindingFlags.Static|BindingFlags.Public);List<Claim> claims =new();foreach(FieldInfofiin fields){if(fi.GetValue(t)isClaim claim)if(claim.Type==AppClaimTypes.Authorization)
                    claims.Add(claim);}return claims.ToArray();}/// <summary>/// Gets an array of all <see cref="AuthorizationClaims"/> where/// Type = <see cref="AppClaimTypes.Credential"/>./// </summary>/// <returns><see cref="T:Claim[]"/></returns>publicstaticClaim[]ToCredentialArray(){Type t =typeof(AuthorizationClaims);FieldInfo[] fields = t.GetFields(BindingFlags.Static|BindingFlags.Public);List<Claim> claims =new();foreach(FieldInfofiin fields){if(fi.GetValue(t)isClaim claim)if(claim.Type==AppClaimTypes.Credential)
                    claims.Add(claim);}return claims.ToArray();}/// <summary>/// Gets an array of all <see cref="AuthorizationClaims"/> where/// Type = <see cref="AppClaimTypes.Challenge"/>./// </summary>/// <returns><see cref="T:Claim[]"/></returns>publicstaticClaim[]ToChallengeArray(){Type t =typeof(AuthorizationClaims);FieldInfo[] fields = t.GetFields(BindingFlags.Static|BindingFlags.Public);List<Claim> claims =new();foreach(FieldInfofiin fields){if(fi.GetValue(t)isClaim claim)if(claim.Type==AppClaimTypes.Challenge)
                    claims.Add(claim);}return claims.ToArray();}}

The authorization claims should be understandable and provide the expected privilege. The AuthorizationClaims class and ToArray methods help to organize and associate claims with users.

AppUser Claims.
AppUser Claims Mobile.

The authorization claims are evaluated by policies. I expect a write claim to include a read privilege and a delete claim to include write and read privileges. Policies are added to the ServicesCollection with the AddAuthorization extension.

If an authorization policy contains multiple authorization requirements, all requirements must pass in order for the policy evaluation to succeed. In other words, multiple authorization requirements added to a single authorization policy are treated on an AND basis.

Program.cs
builder.Services.AddAuthorization(options =>
{
    ...
    options.AddPolicy(AuthorizationPolicies.AppUsersRead, policy =>
    {
        policy.RequireClaim(AppClaimTypes.AppUser);
    });
    options.AddPolicy(AuthorizationPolicies.AppUsersWrite, policy =>
    {
        policy.RequireClaim(AppClaimTypes.AppUser, new string[] { AppClaimValues.Write, AppClaimValues.Delete });
    });
    options.AddPolicy(AuthorizationPolicies.AppUsersDelete, policy =>
    {
        policy.RequireClaim(AppClaimTypes.AppUser, new string[] { AppClaimValues.Delete });
    });
    ...
});

The ASP.NET Core AuthorizeAttribute can restrict access to a page and all page methods by Policy.

namespace UsersWithDevice2FA.Pages.Admin.AppUsers;
[Authorize(Policy = AuthorizationPolicies.AppUsersRead)]
public class IndexModel : PageModel

The AuthorizeAttribute restricts access to all page methods, but more privileged methods can be protected. The AuthorizeAttribute cannot be applied to PageModel methods. Inject the AuthorizationService to the PageModel. Use AuthorizeAsync to verify the requirement. If the requirement is not satisfied the method can throw an exception or redirect to the Access Denied page.

private readonly IUserService _userService;
private readonly IAuthorizationService _authorizationService;
public IndexModel(IUserService userService, IAuthorizationService authorizationService)
{
    _userService = userService;
    _authorizationService = authorizationService;
}
public async Task<IActionResult> OnPostPurgeUsersAsync()
{
    if (!(await _authorizationService.AuthorizeAsync(User, AuthorizationPolicies.AppUsersDelete)).Succeeded)
        return Forbid(); //throw new InvalidOperationException($"Not authorized purge of AppUsers.");

    var allAppUsers = await _userService.GetAppUsers().ToListAsync().ConfigureAwait(false);
    foreach (var appUser in allAppUsers)
        if (!appUser.LoginNameUppercase.Equals("ADMINISTRATOR"))
            if (!await _userService.DeleteAppUserByIdAsync(appUser.Id).ConfigureAwait(false))
                throw new InvalidOperationException($"Error occurred purging users.");

    UsersStatusMessage = "Users purged successfully.";
    return RedirectToPage();
}

Navigation

Navigation to a protected page by a user who does not satisfy the policy requirement is redirected to the Access Denied page.

Program.cs
builder.Services.AddAuthentication(AppSettings.ApplicationScheme)
    .AddCookie(AppSettings.ApplicationScheme, options =>
    {
        options.LoginPath = "/account/login";
        options.LogoutPath = "/account/logout";
        options.AccessDeniedPath = "/account/accessdenied";
        options.ReturnUrlParameter = "returnurl";
        options.SlidingExpiration = true;
        options.Events = new CookieAuthenticationEvents
        {
            OnValidatePrincipal = CookieValidator.ValidateAsync
        };
    })
    .AddCookie(AppSettings.TwoFactorUserIdScheme, options =>
    {
        options.Cookie.Name = AppSettings.TwoFactorUserIdScheme;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
    })
    .AddCookie(AppSettings.TwoFactorRememberMeScheme, options =>
    {
        options.Cookie.Name = AppSettings.TwoFactorRememberMeScheme;
    });
Access Denied.
Access Denied Mobile.

You can avoid Access Denied redirects by excluding navigational controls on the Razor page when a policy's requirement is not satisfied. Inject the AuthorizationService from the ServicesCollection to the Razor page.

_ViewImports.cshtml
...
@using Microsoft.AspNetCore.Authorization;
...
Index.cshtml
@page
@model IndexModel;
@inject IAuthorizationService _authorizationService;

When the page is displayed because the AppUsersRead policy has been satisfied, you can exclude markup when a more privileged policy is not satisfied. The AppUsersWrite policy protects create and edit. The AppUsersDelete policy protects delete.

@if ((await _authorizationService.AuthorizeAsync(User, AuthorizationPolicies.AppUsersWrite)).Succeeded)
{
    <a class="btn btn-primary mb-1 me-2" asp-page="./Create">New</a>
}
Administrator
Administrator AppUser List.
AppUsersRead
Readonly AppUser List.

Email Authorization

Notice the AppUser listing for the AppUsersRead user does not display email addresses.

@if ((await _authorizationService.AuthorizeAsync(User, AuthorizationPolicies.EmailRead)).Succeeded)
{
    <td class="d-none d-xxl-table-cell">
        @Html.DisplayFor(modelItem => item.Email)
    </td>
}

The Hard Parts

How to allow a user with any Authorization claim access the /Admin/Index page. 

The Hard Parts TOC

I developed an Authorization class which implements AuthorizationHandler<Authorization>, IAuthorizationRequirement. Authorization would succeed the requirement if the user had any authorization claim in AuthorizationClaims. ToArray(). Authorization is implemented with the AddRequirements extension.

Program.cs
builder.Services.AddAuthorization(options =>
{
    ...
    options.AddPolicy(AuthorizationPolicies.AdminPanelAccess, policy =>
    {
        policy.AddRequirements(new Authorization(AuthorizationClaims.ToArray()));
    });
    ...
});
Authorization.cs
public class Authorization : AuthorizationHandler<Authorization>, IAuthorizationRequirement
{
    private readonly List<Claim> _claimList;
    public Authorization(List<Claim> claimList)
    {
        _claimList = claimList;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, Authorization authorization)
    {
        if (_claimList != null && _claimList.Any() && context.User.Claims.Intersect(_claimList, new AuthorizationClaimComparer()).Any())
        {
            context.Succeed(authorization);
        }
        return Task.CompletedTask;
    }
}

The Intersect extension requires an IEqualityComparer<Claim>. See IEqualityComparer. GetHashCode(Object) Method.

AuthorizationClaimComparer.cs
public class AuthorizationClaimComparer : IEqualityComparer<Claim>
{
    public bool Equals(Claim? x, Claim? y)
    {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;
        return x.Type == y.Type && x.Value == y.Value && x.ValueType == y.ValueType && x.Issuer == y.Issuer;
    }
    public int GetHashCode(Claim clam)
    {
        return clam.ToString().ToLower().GetHashCode();
    }
}

How to allow a user with an Administrator claim succeed all requirements. 

The Hard Parts TOC

I wanted a super user which could access the entire site without any restriction. I added a new requirement to the Authorization handler. The Contains extension requires the AuthorizationClaimComparer.

public class Authorization : AuthorizationHandler<Authorization>, IAuthorizationRequirement
{
    private readonly List<Claim> _claimList;
    public Authorization(List<Claim> claimList)
    {
        _claimList = claimList;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, Authorization authorization)
    {
        if (context.User.Claims.Contains(AuthorizationClaims.Administrator, new AuthorizationClaimComparer()))
        {
            context.Succeed(authorization);
            return Task.CompletedTask;
        }

        if (_claimList != null && _claimList.Any() && context.User.Claims.Intersect(_claimList, new AuthorizationClaimComparer()).Any())
        {
            context.Succeed(authorization);
            return Task.CompletedTask;
        }
        return Task.CompletedTask;
    }
}

How to isolate Administrators from non-Administrators. 

The Hard Parts TOC

I did not want a user with the AppUserDelete privilege deleting Administrators. I developed a GetAdminAppUserListAsync method which excludes Administrators when IsAdmin is false. This also allowed a super user which could access the entire site and not list or edit Administrators. I added a Security authorization claim for this super user.

AppUsers/Index OnGet
IsAdmin = User.Claims.Contains(AuthorizationClaims.Administrator, new AuthorizationClaimComparer());
AppUsers = await _userService.GetAdminAppUserListAsync(IsAdmin).ConfigureAwait(false);

How to require two-factor authentication enabled for all policies. 

The Hard Parts TOC

The UWD2FAP implements an AppSettings. RequireAdmin2FA option. When set to true, the user must have two-factor authentication enabled to access Admin pages. I added a new requirement to the start of the Authorization handler. If the TwoFactorEnabled user claim's value is false, the handler returns a failed requirement and the policy is denied. The AuthorizationFailureReason is new in .NET 6.0.

if (AppSettings.RequireAdmin2FA)
{
    if (!context.User.HasClaim(c => c.Type == AppClaimTypes.TwoFactorEnabled && c.Value == bool.TrueString &&
        c.ValueType == ClaimValueTypes.Boolean && c.Issuer == AppSettings.ClaimIssuer))
    {
        context.Fail(new AuthorizationFailureReason(authorization, "Two-Factor Authentication must be enabled."));
        return Task.CompletedTask;
    }
}

How to allow a user with any Authorization claim navigate to the /Admin/Index page. 

The Hard Parts TOC

I add a nav-item to the Bootstrap navbar if the user is logged in and has any AuthorizationClaims. If the AdminPanelAccess policy is satisfied, the nav-link navigates to the /Admin/Index page. The user will fail to authorize the AdminPanelAccess policy if AppSettings. RequireAdmin2FA is true and the TwoFactorEnabled user claim's value is false. In this case the nav-link displays "Admin (Disabled)" and navigates to the Two-Factor Authentication page in Manage Account. I demonstrate the new AuthorizationFailureReason feature with a nullable enabled Bootstrap tooltip.

_Layout
@if (User.Identity!.IsAuthenticated && User.Claims.Intersect(AuthorizationClaims.ToArray(), new AuthorizationClaimComparer()).Any())
{
    var authorizationResult = await _authorizationService.AuthorizeAsync(User, AuthorizationPolicies.AdminPanelAccess).ConfigureAwait(false);
    if (authorizationResult.Succeeded)
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-page="/Admin/Index">Admin</a>
        </li>
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-page="/Account/Manage/TwoFactorAuthentication"
                data-bs-toggle="tooltip"
                data-bs-placement="bottom"
                title="@(authorizationResult.Failure?.FailureReasons.FirstOrDefault()?.Message ?? "Failure or FailureReasons was not found.")">
                Admin (Disabled)
            </a>
        </li>
    }
}
管理员已禁用。
管理员禁用的移动设备。

硬质零件的解决方案。 

硬部件目录

当我允许超级:管理员和超级:安全声明满足要求时,授权处理程序变为超级授权。在研究了 RequireClaim 方法之后,我使用重载构造函数实现了声明类型和允许值参数。请参见授权策略生成器。要求索赔方法

超级授权.cs
// Copyright © Ken Haggerty (https://kenhaggerty.com)
// Licensed under the MIT License.
namespace UsersWithDevice2FA.Services.Utilities;
public class SuperAuthorization : AuthorizationHandler<SuperAuthorization>, IAuthorizationRequirement
{
    private readonly string? _claimType;
    private readonly IEnumerable<string>? _allowedValues;
    private readonly IEnumerable<Claim>? _claimArray;
    /// <summary>
    /// Denies authorization when <see cref="AppSettings.RequireAdmin2FA"/> is set true
    /// and the user does not have Two-Factor authentication enabled.
    /// Allows claims where Type = <see cref="AppClaimTypes.Super"/>, satisfy the requirement.
    /// </summary>
    public SuperAuthorization()
    {
    }
    /// <summary>
    /// Denies authorization when <see cref="AppSettings.RequireAdmin2FA"/> is set true
    /// and the user does not have Two-Factor authentication enabled.
    /// Allows claims where Type = <see cref="AppClaimTypes.Super"/>
    /// or any claim in the <paramref name="claimArray"/>, satisfy the requirement.
    /// </summary>
    public SuperAuthorization(IEnumerable<Claim> claimArray)
    {
        _claimArray = claimArray;
    }
    /// <summary>
    /// Denies authorization when <see cref="AppSettings.RequireAdmin2FA"/> is set true
    /// and the user does not have Two-Factor authentication enabled.
    /// Allows claims where Type = <see cref="AppClaimTypes.Super"/>
    /// or any claim where Type = <paramref name="claimType"/>, satisfy the requirement.
    /// </summary>
    public SuperAuthorization(string claimType)
    {
        _claimType = claimType;
    }
    /// <summary>
    /// Denies authorization when <see cref="AppSettings.RequireAdmin2FA"/> is set true
    /// and the user does not have Two-Factor authentication enabled.
    /// Allows claims where Type = <see cref="AppClaimTypes.Super"/>
    /// or any claim where Type = <paramref name="claimType"/> and 
    /// when <paramref name="allowedValues"/> is not null, the Value is in the
    /// <paramref name="allowedValues"/>, satisfy the requirement.
    /// </summary>
    public SuperAuthorization(string claimType, IEnumerable<string>? allowedValues)
    {
        _claimType = claimType;
        _allowedValues = allowedValues;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SuperAuthorization superAuthorization)
    {
        if (AppSettings.RequireAdmin2FA)
        {
            if (!context.User.HasClaim(c => c.Type == AppClaimTypes.TwoFactorEnabled && c.Value == bool.TrueString &&
                c.ValueType == ClaimValueTypes.Boolean && c.Issuer == AppSettings.ClaimIssuer))
            {
                context.Fail(new AuthorizationFailureReason(superAuthorization, "Two-Factor Authentication must be enabled."));
                return Task.CompletedTask;
            }
        }

        var superClaims = AuthorizationClaims.ToSuperArray();
        if (superClaims.Any() && context.User.Claims.Intersect(superClaims, new AuthorizationClaimComparer()).Any())
        {
            context.Succeed(superAuthorization);
            return Task.CompletedTask;
        }

        if (_claimArray != null && _claimArray.Any() && context.User.Claims.Intersect(_claimArray, new AuthorizationClaimComparer()).Any())
        {
            context.Succeed(superAuthorization);
            return Task.CompletedTask;
        }

        if (_claimType != null && _allowedValues == null)
            if (context.User.HasClaim(c => c.Type == _claimType && c.Issuer == AppSettings.ClaimIssuer))
            {
                context.Succeed(superAuthorization);
                return Task.CompletedTask;
            }

        if (_claimType != null && _allowedValues != null && _allowedValues.Any())
            foreach (var value in _allowedValues)
                if (context.User.HasClaim(c => c.Type == _claimType && c.Value == value && c.Issuer==AppSettings.ClaimIssuer)){
                    context.Succeed(superAuthorization);returnTask.CompletedTask;}returnTask.CompletedTask;}}

超级授权处理程序很容易在各种条件下实现。

程序.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy(AuthorizationPolicies.Security, policy =>
    {
        policy.AddRequirements(new SuperAuthorization());
    });
    options.AddPolicy(AuthorizationPolicies.AdminPanelAccess, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AuthorizationClaims.ToArray()));
    });
    options.AddPolicy(AuthorizationPolicies.EmailRead, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.Email));
    });
    options.AddPolicy(AuthorizationPolicies.EmailWrite, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.Email, new string[] { AppClaimValues.Write, AppClaimValues.Send }));
    });
    options.AddPolicy(AuthorizationPolicies.EmailSend, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.Email, new string[] { AppClaimValues.Send }));
    });
    options.AddPolicy(AuthorizationPolicies.AppUsersRead, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.AppUser));
    });
    options.AddPolicy(AuthorizationPolicies.AppUsersWrite, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.AppUser, new string[] { AppClaimValues.Write, AppClaimValues.Delete }));
    });
    options.AddPolicy(AuthorizationPolicies.AppUsersDelete, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.AppUser, new string[] { AppClaimValues.Delete }));
    });
    options.AddPolicy(AuthorizationPolicies.AuthorizationRead, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.Authorization));
    });
    options.AddPolicy(AuthorizationPolicies.AuthorizationAssign, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.Authorization, new string[] { AppClaimValues.Assign }));
    });
    options.AddPolicy(AuthorizationPolicies.CredentialsRead, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.Credential));
    });
    options.AddPolicy(AuthorizationPolicies.CredentialsWrite, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.Credential, new string[] { AppClaimValues.Write, AppClaimValues.Delete }));
    });
    options.AddPolicy(AuthorizationPolicies.CredentialsDelete, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.Credential, new string[] { AppClaimValues.Delete }));
    });
    options.AddPolicy(AuthorizationPolicies.ChallengesRead, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.Challenge));
    });
    options.AddPolicy(AuthorizationPolicies.ChallengesDelete, policy =>
    {
        policy.AddRequirements(new SuperAuthorization(AppClaimTypes.Challenge, new string[] {AppClaimValues.Delete}));});});
肯·哈格蒂
 
创建 01/22/22
 
更新 03/11/22 19:11 GMT

 

ASP.NET Core 6.0 - 基于声明的授权 - KenHaggerty.Com

posted @ 2022-10-24 10:27  岭南春  阅读(216)  评论(0)    收藏  举报