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
webapi框架搭建-安全机制(四)-可配置的基于角色的权限控制的更多相关文章
- webapi框架搭建-安全机制(三)-简单的基于角色的权限控制
webapi框架搭建系列博客 上一篇已经完成了“身份验证”,如果只是想简单的实现基于角色的权限管理,我们基本上不用写代码,微软已经提供了authorize特性,直接用就行. Authorize特性的使 ...
- webapi框架搭建-安全机制(一)
本系列博客链接:webapi框架搭建系列博客 前言 webapi接口是开放给外部使用的,包括接口的地址,传参的规范,还有返回结果的说明.正因为接口的开放性,使得接口的安全很重要.试想一下,用抓包工具( ...
- webapi框架搭建-安全机制(二)-身份验证
webapi框架搭建系列博客 身份验证(authentication)的责任是识别出http请求者的身份,除此之外尽量不要管其它的事.webapi的authentication我用authentica ...
- webapi框架搭建系列博客
webapi框架搭建系列博客 webapi框架搭建-创建项目(一) webapi框架搭建-创建项目(二)-以iis为部署环境的配置 webapi框架搭建-创建项目(三)-webapi owin web ...
- webapi框架搭建-创建项目(二)-以iis为部署环境的配置
上篇:webapi快速框架搭建-创建项目(一) 在"创建项目(一)"这一篇里已经创建了一个空的项目,但项目上什么都没有,本篇描述如何将webapi配置成部署在iis上. 步骤 用n ...
- webapi框架搭建-创建项目(三)-webapi owin
上一篇:创建项目(二) 在上一篇里,我们已经创建好了webapi应用,并已经部署到iis里,本篇讲如何用owin自宿主或是iis宿主来部署webapi应用. owin介绍 传统的asp.net网站只能 ...
- webapi框架搭建-日志管理log4net
前言 本篇讲怎么在前几篇已经创建好的项目里加上日志处理机制,我们采用Log4net技术.跟多的log4net技术的细节请查阅log4net的官网. log4net官网:http://logging.a ...
- webapi框架搭建-webapi异常处理
webapi框架搭建系列博客 前言 上一篇我们已经完成了项目的日志管理,在项目开发中日志会经常记录程序中的异常,供后续问题排查使用.本篇讲如何在webapi里加入异常处理机制. 目的和原则 1.程序任 ...
- webapi框架搭建-数据访问ef code first
webapi框架搭建系列博客 为什么用ef? 我相信很多博友和我一样都有这种“选择困难症”,我曾经有,现在也有,这是技术人的一个通病——总想用“更完美”的方式去实现,导致在技术选择上犹豫不决,或总是推 ...
随机推荐
- 软工实践-Beta 冲刺 (2/7)
队名:起床一起肝活队 组长博客:博客链接 作业博客:班级博客本次作业的链接 组员情况 组员1(队长):白晨曦 过去两天完成了哪些任务 描述: 1.界面的修改与完善 展示GitHub当日代码/文档签入记 ...
- LeetCode题解:(114) Flatten Binary Tree to Linked List
题目说明 Given a binary tree, flatten it to a linked list in-place. For example, Given 1 / \ 2 5 / \ \ 3 ...
- vue中的数据双向绑定
学习的过程是漫长的,只有坚持不懈才能到达到自己的目标. 1.vue中数据的双向绑定采用的时候,数据劫持的模式.其实主要是用了Es5中的Object.defineProperty;来劫持每个属性的get ...
- PHP ini_set
PHP ini_set用来设置php.ini的值,在函数执行的时候生效,对于虚拟空间来说,很方便,下面为大家介绍下此方法的使用 PHP ini_set用来设置php.ini的值,在函数执行的时候生 ...
- [Cnbeta]龙芯处理器性能怎么样
龙芯处理器性能怎么样?下一代CPU同频性能可达英特尔90% 在高性能处理器领域,英特尔是天花顶一般的存在(先不算地位特殊的IBM公司),国内发展CPU处理器的公司很多,绝大多数实力跟英特尔相比都差很远 ...
- Mysql 数据锁与事务
一.锁 常用命令 查看表的存储引擎:mysql> show create table myLock; 修改当前表的存储引擎:mysql> alter table myLock engine ...
- javascript中对象访问自身属性的方式
在javascript中,通过对象的方法访问对象自身属性时,必须采用this.fieldName的方式. 原因是javascript中Function是无状态的,访问对象的属性时,必须指定当前的上下文 ...
- YARN结构分析与工作流程
YARN Architecture Link: http://hadoop.apache.org/docs/r2.7.2/hadoop-yarn/hadoop-yarn-site/YARN.html ...
- tomcat设置虚拟路径映射服务器指定的物理路径
在tomcat的server.xml中的host标签中加入如下标签: <Context crossContext="false" debug="1" do ...
- 【刷题】BZOJ 2005 [Noi2010]能量采集
Description 栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量.在这些植物采集能量后,栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起. 栋栋的植物种得 ...