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? 我相信很多博友和我一样都有这种“选择困难症”,我曾经有,现在也有,这是技术人的一个通病——总想用“更完美”的方式去实现,导致在技术选择上犹豫不决,或总是推 ...
随机推荐
- Java 面试 --- 3
上一篇,我们给出了大概35个题目,都是基础知识,有童鞋反映题目过时了,其实不然,这些是基础中的基础,但是也是必不可少的,面试题目中还是有一些基础题目的,我们本着先易后难的原则,逐渐给出不同级别的题目, ...
- 使用fdisk创建好了分区,但是在生成物理卷出现"Device /dev/sdb2 not found (or ignored by filtering)."解决方法
生成物理卷出现的问题 解决方法 问题所在: 内核没有成功同步分区信息,所有才需要使用"partprobe"命令来手动同步
- Thinkphp面试问题
1.如何理解TP中的单一入口文件? 答:ThinkPHP采用单一入口模式进行项目部署和访问,无论完成什么功能,一个项目都有一个统一(但不一定是唯一)的入口.应该说,所有项目都是从入口文件开始的,并且所 ...
- firewall和iptables
防火墙有这三种方式,firewalld.iptables.ebtables,现在的centOS7使用的是firewalld. 下面是一些总结: 查看当前firewalld的状态 firewall-cm ...
- 一道面试题:StringBuffer a=new StringBuffer ("A"); StringBuffer b=new StringBuffer
前几天又看到这个面试题,再次看看 public class Jtest{ public static void main(String[] args) { StringBuffer a=new Str ...
- 在Asp.Net Core中使用Session
1.在Stratup.cs中配置Session public void ConfigureServices(IServiceCollection services) { services.AddSes ...
- 多线程同步与并发访问共享资源工具—Lock、Monitor、Mutex、Semaphore
“线程同步”的含义 当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如A线程必须等待B和C线程执行完毕之后才能继续执行),则称这些线程需要进行“线程同步(thread synchro ...
- P4596 [COCI2011-2012#5] RAZBIBRIGA
题目描述 四个等长的单词可以放在一起构成一个正方形,两个单词水平放置,两个竖直放置.水平单词只能从左往右读,竖直的单词只能从上往下读.四个角共用一个字母. 图中是由单词HLAD,NIVA,HSIN,D ...
- maven简单理解
前言: maven项目也是一个项目,类似于javaProject,javaWebProject,就是多了些功能,其他也没啥,所以大家接触的时候不要害怕! 1 . 帮你下载jar包 maven项目会有一 ...
- pragma指令详解(转载)
#pragma comment( comment-type [,"commentstring"] ) 该宏放置一个注释到对象文件或者可执行文件.comment-type是一个预定义 ...