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
