webapi框架搭建-安全机制(四)-可配置的基于角色的权限控制
webapi框架搭建系列博客
在上一篇的webapi框架搭建-安全机制(三)-简单的基于角色的权限控制,某个角色拥有哪些接口的权限是用硬编码的方式写在接口上的,如RBAuthorize(Roles = "user,member"),在小的项目里,其实也够用了,但如果项目的需求就是要可在后台管理界面里动态配置某某角色有某某接口的权限怎么办?这编我们一起来实现。
首先,我们要在数据库里存储这些需要权限控制的接口,其次,要在上编的RBAuthorizeAttribute的IsAuthorized方法里重写自己逻辑。
实体设计(数据库表设计)
共4张表:用户表、角色表、资源表、权限表
用户表:只记录用户的基本信息,如id,用户名,姓名,性别,密码等
角色表:只记录角色的基本信息,如角色名,id
资源表:只记录“需要做权限控制的资源”,这些“资源”可以是菜单,接口等。
权限表:记录“哪些角色对哪些资源有访问权限”
用户角色关系表:记录用户和角色的关系
四个表的实体对应如下
用户表
using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace webapi.Entities { /// <summary> /// 用户表 /// </summary> [Table("User")] public partial class User:BaseEntity { /// <summary> /// 主键 /// </summary> [Key, Column(TypeName = "varchar"), MaxLength(50)] public string Id { get; set; } /// <summary> /// 登录名 /// </summary> [Column(TypeName = "varchar"), MaxLength(50)] public string LoginName { get; set; } /// <summary> /// 真实姓名 /// </summary> [Column(TypeName = "varchar"), MaxLength(50)] public string Name { get; set; } /// <summary> /// 密码,用于存储密码的md5加密 /// </summary> [Column(TypeName = "varchar"), MaxLength(50)] public string Pwd { get; set; } /// <summary> /// 性别,1男2女,对应Gender枚举 /// </summary> [Column(TypeName = "int")] public int? Gender { get; set; } /// <summary> /// 身份证 /// </summary> [Column(TypeName = "varchar"), MaxLength(50)] public string IdentityCard { get; set; } /// <summary> /// 电话 /// </summary> [Column(TypeName = "varchar"), MaxLength(50)] public string Tel { get; set; } /// <summary> /// 出生日期 /// </summary> [Column(TypeName = "datetime")] public DateTime? Birthdate { get; set; } /// <summary> /// 头像 /// </summary> [Column(TypeName = "varchar"), MaxLength(500)] public string UserPic { get; set; } } }
角色表
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace webapi.Entities { /// <summary> /// 角色表 /// </summary> [Table("Role")] public partial class Role:BaseEntity { /// <summary> /// 角色ID /// </summary> [Key, Column(TypeName = "varchar"), MaxLength(50)] public string Id { get; set; } /// <summary> /// 角色名 /// </summary> [Column(TypeName = "varchar"), MaxLength(20)] public string Name { get; set; } } }
用户角色关系表
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace webapi.Entities { /// <summary> /// 用户角色关系对应表,user role map /// </summary> [Table("URM")] public partial class URM:BaseEntity { /// <summary> /// ID主键 /// </summary> [Key, Column(TypeName = "varchar"), MaxLength(50)] public string Id { get; set; } /// <summary> /// 用户ID /// </summary> [Column(TypeName = "varchar"), MaxLength(50)] public string UserId { get; set; } /// <summary> /// 角色ID /// </summary> [Column(TypeName = "varchar"), MaxLength(50)] public string RoleId { get; set; } } }
资源表
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace webapi.Entities { /// <summary> /// 需要做权限控制的资源 /// </summary> [Table("Resource")] public class Resource:BaseEntity { /// <summary> /// 主键 /// </summary> [Key,Column(TypeName = "varchar"),MaxLength(50)] public string Id { set; get; } /// <summary> /// 资源类型,如webapi接口,菜单等 /// </summary> [Column(TypeName = "varchar"), MaxLength(20)] public string Category { set; get; } /// <summary> /// 资源名,如“ControllerName.ActionName”,或是“url地址” /// </summary> [Column(TypeName = "varchar"), MaxLength(100)] public string Name { set; get; } /// <summary> /// 资源描述 /// </summary> [Column(TypeName = "varchar"), MaxLength(200)] public string Description { set; get; } } }
权限表
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace webapi.Entities { /// <summary> /// 权限表,记录角色和资源的对应关系 /// </summary> [Table("Permission")] public class Permission:BaseEntity { /// <summary> /// 主键 /// </summary> [Key, Column(TypeName = "varchar"), MaxLength(50)] public string Id { set; get; } /// <summary> /// 资源类型,如webapi接口,菜单等 /// </summary> [Column(TypeName = "varchar"), MaxLength(50)] public string RoleId { set; get; } /// <summary> /// 资源名,如“ControllerName.ActionName”,或是“url地址” /// </summary> [Column(TypeName = "varchar"), MaxLength(50)] public string ResourceId { set; get; } } }
RBAuthorizeAttribute代码修改
核心代码:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Principal; using System.Web.Http; using System.Web.Http.Controllers; using webapi.Entities; using webapi.Services; namespace webapi.Security { /// <summary> /// Role Basic AuthorizeAttribute(基于角色的授权) /// </summary> public class RBAuthorizeAttribute : AuthorizeAttribute { public string Description { set; get; } protected override bool IsAuthorized(HttpActionContext actionContext) { // 下在可替换成自己的授权逻辑代码 AuthorizeService authorizeService =new AuthorizeService(new DB()); var resourceName = actionContext.ActionDescriptor.GetCustomAttributes<RBAuthorizeAttribute>().Any() ? actionContext.ActionDescriptor.ActionName : actionContext.ControllerContext.ControllerDescriptor.ControllerName; var roleNames = authorizeService.GetResourceRoleNames(resourceName); IPrincipal principal = actionContext.ControllerContext.RequestContext.Principal; return principal != null && principal.Identity != null && principal.Identity.IsAuthenticated && ( (((IEnumerable<string>)roleNames).Any<string>(new Func<string, bool>(principal.IsInRole))) ); } protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) { actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "未授权"); } } }
说明:在IsAuthorized方法里判断用户是否有某个资源的权限。下面是我的思路
1)获取用户的角色
在前面的博客里,IdentityBasicAuthentication已经从http请求头里将token解密并知道了用户的角色,并将角色写入到了IPrincipal对象,所以RBAuthorizeAttribute只要从IPrincipal里取出来就行,即如下的代码:IPrincipal principal = actionContext.ControllerContext.RequestContext.Principal;
2)获取该请求资源的所有角色数组
我创建了authorizeService类,用于处理这一个逻辑,博友们可以下载我的框架,只要改一下authorizeService类里的GetResourceRoleNames方法就行。
3)判断是否有权限
首先principal不能为空,且principal.Identity是已经通过身份验证的(即Identity.IsAuthenticated==true),并且用户的角色在资源权限角色数组里。
return principal != null && principal.Identity != null && principal.Identity.IsAuthenticated && ( (((IEnumerable<string>)roleNames).Any<string>(new Func<string, bool>(principal.IsInRole))) );
各辅助代码也附上
AuthorizeService代码
using System; using System.Collections.Generic; using System.Linq; using System.Web; using webapi.Entities; namespace webapi.Services { public class AuthorizeService:BaseService { public AuthorizeService(DB db) : base(db) { } /// <summary> /// 获取资源的角色名数组 /// </summary> /// <param name="resourceName"></param> /// <returns></returns> public string[] GetResourceRoleNames(string resourceName) { var resource=_db.Resources.FirstOrDefault(a => a.Name == resourceName); var roleIds = _db.Permissions.Where(a => a.ResourceId == resource.Id).Select(a => a.RoleId).ToArray(); var roleNames = _db.Roles.Where(a => roleIds.Contains(a.Id)).Select(a => a.Name).ToArray(); return roleNames; } } }
源码地址:https://github.com/shengyu-kmust/webapi.git