2

我试图在这里围绕几个概念展开思考,但我不希望这个问题过于宽泛——基本上我们要做的是使用角色声明作为锁定我们 API 的权限,但我发现 access_token 是变得太大。

我们在服务器端使用 OpenIddict 和 ASP.NET Identity 3。我们已经实现了默认的 AspNetRoleClaims 表来存储我们对每个角色的声明 - 将它们用作权限。

我们使用基于自定义策略的声明授权来锁定我们的 API 端点,如下所示:

基于自定义策略的授权

我发现的主要问题是包含我们声明的 access_token 变得非常大。我们正在尝试使数据库中的 ClaimType 和 Value 非常小,以使声明占用空间更小。我们有一个基本的 CRUD 类型权限方案,因此对于我们的 SPA 客户端应用程序中的每个“模块”或屏幕,有 4 个权限。我们添加到应用程序中的模块越多,access_token 中的声明就越多,并且我们的 Authorization Bearer 标头变得非常大。我担心随着应用程序的增长,这变得不是很有可扩展性。

因此,声明嵌入在 access_token 中,当我点击我的端点时,该端点被这样的自定义策略锁定......

[Authorize(Policy="MyModuleCanRead")]
[HttpGet]
public IEnumerable<MyViewModel> Get()

然后我可以在 AuthorizationHandler 中访问我的 ASP.NET 身份用户和 User.Claims。

如果这是一个明显的问题,请提前抱歉 - 但我想知道 - 为了让基于自定义策略的授权工作 - 是否绝对需要声明在 id_token 或 access_token 中才能调用处理程序?

如果我从 access_token 中删除声明,那么我的 AuthorizationHandler 代码不会被命中,并且我无法访问被我的自定义策略锁定的端点。

我想知道是否可以使用自定义声明策略,但在授权处理程序中具有检查声明的实际代码,以便声明不会随每个 HTTP 请求一起传递,而是从授权 cookie 获取服务器端或从数据库。

* 更新 *

Pintpoint 使用授权处理程序的答案以及关于如何从 cookie 中删除额外角色声明的评论实现了我想要的。

万一这对其他人有帮助 - 这是覆盖 UserClaimsPrincipalFactory 并防止将角色声明写入 cookie 的代码。(我有许多角色声明,因为权限和 cookie 和请求标头变得太大)

public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
    public AppClaimsPrincipalFactory(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
    {
    }
    public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
    {
        if (user == null)
        {
            throw new ArgumentNullException(nameof(user));
        }
        var userId = await UserManager.GetUserIdAsync(user);
        var userName = await UserManager.GetUserNameAsync(user);
        var id = new ClaimsIdentity(Options.Cookies.ApplicationCookieAuthenticationScheme,
            Options.ClaimsIdentity.UserNameClaimType,
            Options.ClaimsIdentity.RoleClaimType);
        id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId));
        id.AddClaim(new Claim(Options.ClaimsIdentity.UserNameClaimType, userName));
        if (UserManager.SupportsUserSecurityStamp)
        {
            id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
                await UserManager.GetSecurityStampAsync(user)));
        }

        // code removed that adds the role claims 

        if (UserManager.SupportsUserClaim)
        {
            id.AddClaims(await UserManager.GetClaimsAsync(user));
        }
        return new ClaimsPrincipal(id);
    }
}
4

1 回答 1

1

我想知道是否可以使用自定义声明策略,但在授权处理程序中具有检查声明的实际代码,以便声明不会随每个 HTTP 请求一起传递,而是从授权 cookie 获取服务器端或从数据库。

这绝对是可能的。您可以这样做:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Has-Edit-User-Profiles-Permission", builder =>
            {
                builder.RequirePermission("Edit-User-Profiles");
            });
        });
    }
}

public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
    public PermissionAuthorizationRequirement(string permission)
    {
        if (string.IsNullOrEmpty(permission))
        {
            throw new ArgumentException("The permission cannot be null or empty.", nameof(permission));
        }

        Permission = permission;
    }

    public string Permission { get; set; }
}

public class PermissionAuthorizationHandler :
    AuthorizationHandler<PermissionAuthorizationRequirement>
{
    private readonly UserManager<ApplicationUser> _userManager;

    public PermissionAuthorizationHandler(UserManager<ApplicationUser> userManager)
    {
        if (userManager == null)
        {
            throw new ArgumentNullException(nameof(userManager));
        }

        _userManager = userManager;
    }

    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        PermissionAuthorizationRequirement requirement)
    {
        if (context.User == null)
        {
            return;
        }

        var user = await _userManager.GetUserAsync(context.User);
        if (user == null)
        {
            return;
        }

        // Use whatever API you need to ensure the user has the requested permission.
        if (await _userManager.IsInRoleAsync(user, requirement.Permission))
        {
            context.Succeed(requirement);
        }
    }
}

public static class PermissionAuthorizationExtensions
{
    public static AuthorizationPolicyBuilder RequirePermission(
        this AuthorizationPolicyBuilder builder, string permission)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }

        if (string.IsNullOrEmpty(permission))
        {
            throw new ArgumentException("The permission cannot be null or empty.", nameof(permission));
        }

        return builder.AddRequirements(new PermissionAuthorizationRequirement(permission));
    }
}
于 2017-03-04T18:38:01.970 回答