三、基于声明授权
对于上例来说,本质上也是基于声明(Claim)的授权,因为张三的"TestPutBookRole"角色也是作为一个Claim添加到证书中的。只不过采用了特定的ClaimTypes.Role。那么是否可以将其他的普通Claim作为授权的依据呢?当然是可以的。
这里涉及到了另一个单词“Policy”,翻译为策略?也就是说,可以把一系列的规则(例如要求姓名为李四,账号为002,国籍为中国等等)组合在一起,形成一个Policy,只有满足这个Policy的才可以被授权访问。
下面我们就新建一个Policy,在Startup的ConfigureServices中添加授权代码:
services.AddAuthorization(options=>options.AddPolicy("Name",policy=> { policy.RequireClaim(ClaimTypes.Name, "张三"); policy.RequireClaim(ClaimTypes.NameIdentifier,"001"); }));
在BookController中添加一个Action如下
[HttpDelete] [Authorize(Policy = "TestPolicy")] public JsonResult Delete() { return new JsonResult("Delete Book ..."); }
可以通过张三和李四的账号测试一下,只有使用张三的账号获取的Token能访问成功。
四、基于策略自定义授权
上面介绍了两种授权方式,现在有个疑问,通过角色授权,只适合一些小型项目,将几个功能通过角色区分开就可以了。
通过声明的方式,目测实际项目中需要在Startup中先声明一系列的Policy,然后在Controller或Action中使用。
这两种方式都感觉不好。例如经常存在这样的需求:一个用户可以有多个角色,每个角色对应多个可访问的API地址(将授权细化到具体的Action)。用户还可以被特殊的授予某个API地址的权限。
这样的需求采用上面的两种方式实现起来都很麻烦,好在ASP.NET Core提供了方便的扩展方式。
1.样例数据
将上面的需求汇总一下,最终可以形成如下形式的数据:
/// <summary> /// 虚拟数据,模拟从数据库或缓存中读取用户相关的权限 /// </summary> public static class TemporaryData { public readonly static List<UserPermissions> UserPermissions = new List<UserPermissions> { new UserPermissions { Code = "001", Permissions = new List<Permission> { new Permission { Code = "A1", Name = "student.create", Url = "/api/student",Method="post" }, new Permission { Code = "A2", Name = "student.delete", Url = "/api/student",Method="delete"} } }, new UserPermissions { Code = "002", Permissions = new List<Permission> { new Permission { Code = "B1", Name = "book.create", Url = "/api/book" ,Method="post"}, new Permission { Code = "B2", Name = "book.delete", Url = "/api/book" ,Method="delete"} } }, }; public static UserPermissions GetUserPermission(string code) { return UserPermissions.FirstOrDefault(m => m.Code.Equals(code)); } }
涉及到的两个类如下:
public class Permission { public string Code { get; set; } public string Name { get; set; } public string Url { get; set; } public string Method { get; set; } } public class UserPermissions { public string Code { get; set; } public List<Permission> Permissions { get; set; } }
2.自定义处理程序
下面就是根据样例数据来制定相应的处理程序了。这涉及到IAuthorizationRequirement和AuthorizationHandler两个内容。
IAuthorizationRequirement是一个空的接口,主要用于提供授权所需要满足的“要求”,或者说是“规则”。AuthorizationHandler则是对请求和“要求”的联合处理。
新建一个PermissionRequirement实现IAuthorizationRequirement接口。
public class PermissionRequirement: IAuthorizationRequirement { public List<UserPermissions> UsePermissionList { get { return TemporaryData.UserPermissions; } } }
很简单的内容。它的“要求”也就是用户的权限列表了,用户的权限列表中包含当前访问的API,则授权通过,否则不通过。
判断逻辑放在新建的PermissionHandler中:
public class PermissionHandler : AuthorizationHandler<PermissionRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { var code = context.User.Claims.FirstOrDefault(m => m.Type.Equals(ClaimTypes.NameIdentifier)); if (null != code) { UserPermissions userPermissions = requirement.UsePermissionList.FirstOrDefault(m => m.Code.Equals(code.Value.ToString())); var Request = (context.Resource as AuthorizationFilterContext).HttpContext.Request; if (null != userPermissions && userPermissions.Permissions.Any(m => m.Url.ToLower().Equals(Request.Path.Value.ToLower()) && m.Method.ToLower().Equals(Request.Method.ToLower()) )) { context.Succeed(requirement); } else { context.Fail(); } } else { context.Fail(); } return Task.CompletedTask; } }
逻辑很简单不再描述。
3.使用自定义的处理程序
在Startup的ConfigureServices中添加授权代码
services.AddAuthorization(options => options.AddPolicy("Permission", policy => policy.Requirements.Add(new PermissionRequirement()))); services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
将BookController的Delete Action修改一下:
[HttpDelete] //[Authorize(Policy = "TestPolicy")] [Authorize(Policy = "Permission")] public JsonResult Delete() { return new JsonResult("Delete Book ..."); }
测试一下只有李四可以访问这个Action。