3 回答

TA貢獻1836條經驗 獲得超3個贊
編輯:根據反饋使其動態化
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, "[email protected]")
};
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);
}
}
要連接起來,只需在啟動類中使用IApplicationBuilder的UseMiddleware擴展方法。
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" };
}
以下是測試場景:
正:https://localhost:44337/api/values?tenant=tenant1
否定:https://localhost:44337/api/values?tenant=tenant2
否定:https://localhost:44337/api/values
這種方法的關鍵是我從未實際返回 403。代碼設置身份,然后讓框架處理結果。這確保身份驗證與授權分開。

TA貢獻1816條經驗 獲得超4個贊
您可以創建自己的屬性來檢查用戶的角色。我在我的一個應用程序中這樣做了:
public sealed class RoleValidator : Attribute, IAuthorizationFilter
{
private readonly IEnumerable<string> _roles;
public RoleValidator(params string[] roles) => _roles = roles;
public RoleValidator(string role) => _roles = new List<string> { role };
public void OnAuthorization(AuthorizationFilterContext filterContext)
{
if (filterContext.HttpContext.User.Claims == null || filterContext.HttpContext.User.Claims?.Count() <= 0)
{
filterContext.Result = new UnauthorizedResult();
return;
}
if (CheckUserRoles(filterContext.HttpContext.User.Claims))
return;
filterContext.Result = new ForbidResult();
}
private bool CheckUserRoles(IEnumerable<Claim> claims) =>
JsonConvert.DeserializeObject<List<RoleDto>>(claims.FirstOrDefault(x => x.Type.Equals(ClaimType.Roles.ToString()))?.Value)
.Any(x => _roles.Contains(x.Name));
}
它從聲明中獲取用戶角色,并檢查用戶是否具有獲取此資源的適當角色。你可以像這樣使用它:
[RoleValidator("Admin")]
或更好的枚舉方法:
[RoleValidator(RoleType.Admin)]
或者您可以傳遞多個角色:
[RoleValidator(RoleType.User, RoleType.Admin)]
對于此解決方案,您還必須使用標準授權屬性。

TA貢獻1770條經驗 獲得超3個贊
根據評論編輯
根據我的理解,您想訪問當前用戶(所有相關信息)、您要為控制器(或操作)指定的角色以及端點接收的參數。還沒有嘗試過 web api,但對于 asp.net core MVC,您可以通過AuthorizationHandler在基于策略的授權中使用并結合專門創建的注入服務來確定角色資源訪問來實現這一點。
為此,首先在以下位置設置政策Startup.ConfigureServices:
services.AddAuthorization(options =>
{
options.AddPolicy("UserResource", policy => policy.Requirements.Add( new UserResourceRequirement() ));
});
services.AddScoped<IAuthorizationHandler, UserResourceHandler>();
services.AddScoped<IRoleResourceService, RoleResourceService>();
接下來創建UserResourceHandler:
public class UserResourceHandler : AuthorizationHandler<UserResourceRequirement>
{
readonly IRoleResourceService _roleResourceService;
public UserResourceHandler (IRoleResourceService r)
{
_roleResourceService = r;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext authHandlerContext, UserResourceRequirement requirement)
{
if (context.Resource is AuthorizationFilterContext filterContext)
{
var area = (filterContext.RouteData.Values["area"] as string)?.ToLower();
var controller = (filterContext.RouteData.Values["controller"] as string)?.ToLower();
var action = (filterContext.RouteData.Values["action"] as string)?.ToLower();
var id = (filterContext.RouteData.Values["id"] as string)?.ToLower();
if (_roleResourceService.IsAuthorize(area, controller, action, id))
{
context.Succeed(requirement);
}
}
}
}
訪問端點接收到的參數是通過轉換context.Resource為 來實現的AuthorizationFilterContext,這樣我們就可以RouteData從它訪問。至于UserResourceRequirement,我們可以留空。
public class UserResourceRequirement : IAuthorizationRequirement { }
至于IRoleResourceService,它是一個普通的服務類,因此我們可以向它注入任何東西。此服務是將角色與代碼中的動作配對的替代品,因此我們無需在動作的屬性中指定它。這樣,我們就可以自由選擇實現方式,例如:來自數據庫、來自配置文件或硬編碼。
訪問用戶RoleResourceService是通過注入實現的IHttpContextAccessor。請注意,要使IHttpContextAccessor可注射,請services.AddHttpContextAccessor()在Startup.ConfigurationServices方法體中添加。
這是從配置文件獲取信息的示例:
public class RoleResourceService : IRoleResourceService
{
readonly IConfiguration _config;
readonly IHttpContextAccessor _accessor;
readonly UserManager<AppUser> _userManager;
public class RoleResourceService(IConfiguration c, IHttpContextAccessor a, UserManager<AppUser> u)
{
_config = c;
_accessor = a;
_userManager = u;
}
public bool IsAuthorize(string area, string controller, string action, string id)
{
var roleConfig = _config.GetValue<string>($"RoleSetting:{area}:{controller}:{action}"); //assuming we have the setting in appsettings.json
var appUser = await _userManager.GetUserAsync(_accessor.HttpContext.User);
var userRoles = await _userManager.GetRolesAsync(appUser);
// all of needed data are available now, do the logic of authorization
return result;
}
}
從數據庫中獲取設置肯定有點復雜,但它可以完成,因為我們可以注入AppDbContext。對于硬編碼方法,有很多方法可以做到。
完成所有操作后,對操作使用策略:
[Authorize(Policy = "UserResource")] //dont need Role name because of the RoleResourceService
public ActionResult<IActionResult> GetSomething(int resourceId)
{
//existing code
}
事實上,我們可以對我們想要應用的任何操作使用“UserResource”策略。
- 3 回答
- 0 關注
- 176 瀏覽
添加回答
舉報