在上一篇的webapi框架搭建-安全機制(三)-簡單的基於角色的權限控制,某個角色擁有哪些接口的權限是用硬編碼的方式寫在接口上的,如RBAuthorize(Roles = "user,member"),在小的項目裏,其實也夠用了,但若是項目的需求就是要可在後臺管理界面裏動態配置某某角色有某某接口的權限怎麼辦?這編咱們一塊兒來實現。html
首先,咱們要在數據庫裏存儲這些須要權限控制的接口,其次,要在上編的RBAuthorizeAttribute的IsAuthorized方法裏重寫本身邏輯。git
共4張表:用戶表、角色表、資源表、權限表github
用戶表:只記錄用戶的基本信息,如id,用戶名,姓名,性別,密碼等web
角色表:只記錄角色的基本信息,如角色名,id數據庫
資源表:只記錄「須要作權限控制的資源」,這些「資源」能夠是菜單,接口等。api
權限表:記錄「哪些角色對哪些資源有訪問權限」數組
用戶角色關係表:記錄用戶和角色的關係安全
四個表的實體對應以下框架
用戶表ide
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; } } }
核心代碼:
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