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? 我相信很多博友和我一样都有这种“选择困难症”,我曾经有,现在也有,这是技术人的一个通病——总想用“更完美”的方式去实现,导致在技术选择上犹豫不决,或总是推 ...
随机推荐
- linux 常用命令-编辑模式
1.编辑模式就是通过vi或者vim打包文件,进入编辑模式,vim是vi的升级版,vim除了报错vi的命令外还包括一些额外的命令,本文以vim命令为例,如果需要查询而不需要编辑文件则可以通过cat命令查 ...
- 如何在mvc项目中使用apiController
文章地址:How do you route from an MVC project to an MVC ApiController in another project? 文章地址:How to Us ...
- APP案例分析-热血江湖
本次分析的是一款游戏名叫热血江湖,这是我上小学的时候就在玩的游戏,可以说是一直玩到现在所以对它有一定的感情,所以决定分析这款游戏.下面附一张现在的游戏登陆界面. 第一部分 调研, 评测 1.下载软件并 ...
- Http的响应结构
Http响应结构有三部分组成: Http头部(Http Header):它们包含了更多关于响应的信息.比如:头部可以指定认为响应过期的过期日期,或者是指定用来给用户安全的传输实体内容的编码格式.如何在 ...
- 蜗牛慢慢爬 LeetCode 9. Palindrome Number [Difficulty: Easy]
题目 Determine whether an integer is a palindrome. Do this without extra space. Some hints: Could nega ...
- 使用JavascriptExecutor改变页面元素
如下如html的页面代码 <html> <body> <input type="text" name="text" value=& ...
- vue 笔记1
created 钩子可以用来在一个实例被创建之后执行代码: new Vue({ data: { a: 1 }, created: function () { // `this` 指向 vm 实例 co ...
- js & 快捷键 & vue bind bug
js & 快捷键 & vue bind bug how to prevent addEventListener bind many times solution dataset &am ...
- linux 实践到的命令 collection
查看文件夹/文件 大小:du :(disk usage) 要通过 1024 字节块概述一个目录树及其每个子树的磁盘使用情况,请输入: du -k /home/fran/filename 这在/ho ...
- LaTex Font Size 字体大小
目录 命令 效果图 命令 LaTex中字体大小由以下命令控制: \tiny \scriptsize \footnotesize \small \normalsize \large \Large \LA ...