背景
前面我们介绍了通过对自定义授权策略和自定义授权处理程序的使用实现了基本的RBAC权限设计,将大量的用户可访问资源及操作的标识直接放到用户的 JWT Token 中显然并不合适,这篇文章我们主要介绍通过中间件如何根据用户的角色添加用户的 Claim
。
实现
角色获取
首先我们需要提供一个接口 IRolePermission
,需要用户自行实现 GetRolePermissionClaimsByName
通过角色名获取用户的 List<Claim>
。这里当然也可将用户自身拥有的特定 Claim
也加入进去。
public interface IRolePermission
{
/// <summary>
/// 获取角色的所有 Permission
/// </summary>
/// <param name="roleName"></param>
/// <returns></returns>
Task<List<Claim>> GetRolePermissionClaimsByName(string roleName);
}
中间件核心逻辑
创建中间件 RolePermissionMiddleware
,通过 DI 注入 IRolePermission rolePermission
。核心的执行逻辑为:
/// <summary>
/// 自定义中间件要执行的逻辑
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
}
要确保用户信息存在
if (context.User is null)
{
await _next(context);
return;
}
这里我们提供了一个可选的参数,使中间件可以单独使用,也可以仅在含有ResourceAttribute
标记时执行。
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
if (endpoint is null)
{
await _next(context);
return;
}
var endpointMetaData = endpoint!.Metadata;
bool hasResourceAttribute = endpointMetaData.Any(x => x is ResourceAttribute);
if (!hasResourceAttribute)
{
await _next(context);
return;
}
该中间件主要的核心逻辑为读取用户所有的角色,然后查询角色对应的权限将其放入。
// 获取用户的所有角色
var roles = context.User.FindAll(ClaimTypes.Role);
// 逐个获取角色的 claims 并添加给 User
foreach (var role in roles.ToList())
{
var roleclaims = await _rolePermission.GetRolePermissionClaimsByName(role.Value);
if (roleclaims.Count() > 0)
{
context.User.AddIdentity(new ClaimsIdentity(roleclaims));
}
}
中间件注册
中间件的注册提供了可选的参数,同时需要添加用户角色查询服务。添加RolePermissionExtensions
:
/// <summary>
/// 添加根据角色名为 User 加入角色 Permission 的中间件
/// </summary>
/// <param name="app"></param>
/// <param name="configureOptions"></param>
/// <returns></returns>
public static IApplicationBuilder UseRolePermission(this IApplicationBuilder app, Action<RolePermissionOptions> configureOptions)
{
var options = new RolePermissionOptions();
configureOptions(options);
return app.UseMiddleware<RolePermissionMiddleware>(options);
}
/// <summary>
/// 添加根据角色名为 User 加入角色 Permission 的中间件
/// </summary>
/// <param name="app"></param>
/// <returns></returns>
public static IApplicationBuilder UseRolePermission(this IApplicationBuilder app)
{
return app.UseMiddleware<RolePermissionMiddleware>(new RolePermissionOptions());
}
/// <summary>
/// 添加角色权限查询服务
/// </summary>
/// <typeparam name="RolePermission">获取角色权限的实现</typeparam>
/// <param name="Services"></param>
public static void AddRolePermission<RolePermission>(this IServiceCollection Services) where RolePermission : class, IRolePermission
{
Services.AddSingleton<IRolePermission, RolePermission>();
}
最后
需要特别注意的是,这个中间件启用的位置。需要在 UseAuthentication
之后 UseAuthorization
之前,也就是说要在验证了用户后,开始检查用户权限前将用户的角色权限赋予给 context.User
。
本文介绍的相关代码已经提供 Nuget 包,并开源了代码,感兴趣的同学可以查阅:
https://github.com/sangyuxiaowu/Sang.AspNetCore.RoleBasedAuthorization
如有错漏之处,敬请指正。