gpt4 book ai didi

c# - 如何在不重复 if/else 代码的情况下使用 ASP.NET Core 基于资源的授权

转载 作者:太空狗 更新时间:2023-10-29 20:15:38 26 4
gpt4 key购买 nike

我有一个 dotnet 核心 2.2 api,其中包含一些 Controller 和操作方法,需要根据用户声明和正在访问的资源进行授权。基本上,每个用户可以对每个资源拥有 0 个或多个“角色”。这一切都是使用 ASP.NET Identity Claims 完成的。

所以,我的理解是我需要利用 Resource-based authorization .但这两个示例大部分相同,并且在每个操作方法上都需要明确的命令式 if/else 逻辑,这是我试图避免的。

我希望能够做类似的事情

[Authorize("Admin")] // or something similar
public async Task<IActionResult> GetSomething(int resourceId)
{
var resource = await SomeRepository.Get(resourceId);

return Json(resource);
}

在其他地方将授权逻辑定义为策略/过滤器/要求/任何东西,并可以访问当前用户声明和端点接收的 resourceId 参数。因此,我可以查看用户是否有声明表明他对特定的 resourceId 具有“管理员”角色。

最佳答案

编辑:根据反馈使其动态化

RBAC 和 .NET 中声明的关键是创建您的 ClaimsIdentity,然后让框架完成它的工作。下面是一个示例中间件,它将查看查询参数“user”,然后根据字典生成 ClaimsPrincipal。

为了避免实际连接到身份提供者的需要,我创建了一个中间件来设置 ClaimsPrincipal:

// **THIS CLASS IS ONLY TO DEMONSTRATE HOW THE ROLES NEED TO BE SETUP **
public class CreateFakeIdentityMiddleware
{
private readonly RequestDelegate _next;

public CreateFakeIdentityMiddleware(RequestDelegate next)
{
_next = next;
}

private readonly Dictionary<string, string[]> _tenantRoles = new Dictionary<string, string[]>
{
["tenant1"] = new string[] { "Admin", "Reader" },
["tenant2"] = new string[] { "Reader" },
};

public async Task InvokeAsync(HttpContext context)
{
// Assume this is the roles
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "John"),
new Claim(ClaimTypes.Email, "john@someemail.com")
};

foreach (KeyValuePair<string, string[]> tenantRole in _tenantRoles)
{
claims.AddRange(tenantRole.Value.Select(x => new Claim(ClaimTypes.Role, $"{tenantRole.Key}:{x}".ToLower())));
}

// Note: You need these for the AuthorizeAttribute.Roles
claims.AddRange(_tenantRoles.SelectMany(x => x.Value)
.Select(x => new Claim(ClaimTypes.Role, x.ToLower())));

context.User = new System.Security.Claims.ClaimsPrincipal(new ClaimsIdentity(claims,
"Bearer"));

await _next(context);
}
}

要连接起来,只需使用 UseMiddleware IApplicationBuilder 的扩展方法在你的创业类。

app.UseMiddleware<RBACExampleMiddleware>();

我创建了一个 AuthorizationHandler,它将查找查询参数“租户”,并根据角色成功或失败。

public class SetTenantIdentityHandler : AuthorizationHandler<TenantRoleRequirement>
{
public const string TENANT_KEY_QUERY_NAME = "tenant";

private static readonly ConcurrentDictionary<string, string[]> _methodRoles = new ConcurrentDictionary<string, string[]>();

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TenantRoleRequirement requirement)
{
if (HasRoleInTenant(context))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}

private bool HasRoleInTenant(AuthorizationHandlerContext context)
{
if (context.Resource is AuthorizationFilterContext authorizationFilterContext)
{
if (authorizationFilterContext.HttpContext
.Request
.Query
.TryGetValue(TENANT_KEY_QUERY_NAME, out StringValues tenant)
&& !string.IsNullOrWhiteSpace(tenant))
{
if (TryGetRoles(authorizationFilterContext, tenant.ToString().ToLower(), out string[] roles))
{
if (context.User.HasClaim(x => roles.Any(r => x.Value == r)))
{
return true;
}
}
}
}

return false;
}

private bool TryGetRoles(AuthorizationFilterContext authorizationFilterContext,
string tenantId,
out string[] roles)
{
string actionId = authorizationFilterContext.ActionDescriptor.Id;
roles = null;

if (!_methodRoles.TryGetValue(actionId, out roles))
{
roles = authorizationFilterContext.Filters
.Where(x => x.GetType() == typeof(AuthorizeFilter))
.Select(x => x as AuthorizeFilter)
.Where(x => x != null)
.Select(x => x.Policy)
.SelectMany(x => x.Requirements)
.Where(x => x.GetType() == typeof(RolesAuthorizationRequirement))
.Select(x => x as RolesAuthorizationRequirement)
.SelectMany(x => x.AllowedRoles)
.ToArray();

_methodRoles.TryAdd(actionId, roles);
}

roles = roles?.Select(x => $"{tenantId}:{x}".ToLower())
.ToArray();

return roles != null;
}
}

TenantRoleRequirement 是一个非常简单的类:

public class TenantRoleRequirement : IAuthorizationRequirement { }

然后像这样在 startup.cs 文件中连接所有内容:

services.AddTransient<IAuthorizationHandler, SetTenantIdentityHandler>();

// Although this isn't used to generate the identity, it is needed
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "https://localhost:5000/";
options.Authority = "https://localhost:5000/identity/";
});

services.AddAuthorization(authConfig =>
{
authConfig.AddPolicy(Policies.HasRoleInTenant, policyBuilder => {
policyBuilder.RequireAuthenticatedUser();
policyBuilder.AddRequirements(new TenantRoleRequirement());
});
});

方法如下所示:

// TOOD: Move roles to a constants/globals
[Authorize(Policy = Policies.HasRoleInTenant, Roles = "admin")]
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}

以下是测试场景:

  1. 正:https://localhost:44337/api/values?tenant=tenant1

  2. 否定:https://localhost:44337/api/values?tenant=tenant2

  3. 否定:https://localhost:44337/api/values

这种方法的关键是我从未实际返回 403。代码设置身份,然后让框架处理结果。这确保身份验证与授权分开。

关于c# - 如何在不重复 if/else 代码的情况下使用 ASP.NET Core 基于资源的授权,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56797173/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com