[认证授权] 6.Permission Based Access Control
在前面5篇博客中介绍了OAuth2和OIDC(OpenId Connect),其作用是授权和认证。那么当我们得到OAuth2的Access Token或者OIDC的Id Token之后,我们的资源服务如何来验证这些token是否有权限来执行对资源的某一项操作呢?比如我有一个API,/books,它具有如下5个操作:
| POST /books | 添加一本书 |
| GET /books/{id} | 获取一本书 |
| PUT /books/{id} | 更新一本书 |
| DELETE /books/{id} | 删除一本书 |
| GET /books | 获取书的列表 |
其伪代码如下:
[Route("books")]
public class BooksController : Controller
{
[HttpGet("")]
public Book[] Get() { return null; }
[HttpGet("{bookId}")]
public Book Get(int bookId) { return null; }
[HttpPost("")]
public Book Post(Book book) { return null; }
[HttpPut("{bookId}")]
public Book Put(int bookId, Book book) { return null; }
[HttpDelete("{bookId}")]
public Book Delete(int bookId) { return null; }
}
那么我们先看看基于OAuth2的Access Token,OIDC的Id Token和传统的基于角色的权限控制是如何处理控制这些资源的操作。
1 OAuth2的Access Token之Scope
我们都知道OAuth2的最终产物是提供给我们一个Access Token,而这个Access Token中包含了一个Scope的字段,这个字段代表的是授权服务器或者资源拥有者授予第三方客户端允许操作资源服务器的哪些资源的范围。这里有一点需要注意的是,这个授权过程可以有资源拥有着的参与(Authorization Code,Implicit,Resource Owner Password Credentials Grant),也可以没有他的参与(Client Credentials Grant)。那么基于上述的books的资源,我们可以定义一个 book_manager 的Scope,来控制对books的五个操作的权限控制。那么Books的基于Scope的权限控制看起来就像是这样的:
[Route("books")]
public class BooksController : Controller
{
[HttpGet("")]
[Scope("book_manager")]
public Book[] Get() { return null; }
[HttpGet("{bookId}")]
[Scope("book_manager")]
public Book Get(int bookId) { return null; }
[HttpPost("")]
[Scope("book_manager")]
public Book Post(Book book) { return null; }
[HttpPut("{bookId}")]
[Scope("book_manager")]
public Book Put(int bookId, Book book) { return null; }
[HttpDelete("{bookId}")]
[Scope("book_manager")]
public Book Delete(int bookId) { return null; }
}
注意看红色的部分,为每一个操作都添加了一个Scope的描述。如果Access Token拥有book_manager这个Scope(不管他是OAuth2的哪一个授权方式颁发的,我们的最终代码部分只认Scope),那么对这些API的调用就是被允许的,否则视为无权操作。
2 OIDC的Id Token之sub
关于Id Token的用途以及其包含哪些信息请参考Id Token。Id Token和Access Token的不同之处在于它一定是包含某一个用户的标识 sub ,但是没有Scope,这是因为Id Token的用途是认证当前用户是谁,所以用户是必须存在的;由于仅仅是认证,则不会包含被认证用户可以做哪些操作之类的授权相关的事情。那么针对Id Token,我们的API应该如何进行权限管控呢?通常的做法是使用传统的基于校色的权限控制(Role Based Access Control)。其实现细节就不解释了,它的模型大致是:一个实体(用户或者组织)拥有一组角色,每一个角色代表着一组权限集合。感觉是不是和Scope很像呢,其实差不多。我们定义一个这样的角色 图书管理员 吧。这里是故意和Scope的命名区分开的,因为其来源不同,那么我们最终实现的时候也会是独立开来的。
[Route("books")]
public class BooksController : Controller
{
[HttpGet("")]
[Scope("book_manager")]
[Role("图书管理员")]
public Book[] Get() { return null; }
[HttpGet("{bookId}")]
[Scope("book_manager")]
[Role("图书管理员")]
public Book Get(int bookId) { return null; }
[HttpPost("")]
[Scope("book_manager")]
[Role("图书管理员")]
public Book Post(Book book) { return null; }
[HttpPut("{bookId}")]
[Scope("book_manager")]
[Role("图书管理员")]
public Book Put(int bookId, Book book) { return null; }
[HttpDelete("{bookId}")]
[Scope("book_manager")]
[Role("图书管理员")]
public Book Delete(int bookId) { return null; }
}
如果 sub 代表的用户自身拥有或者其所属的组织机构拥有(不管其是怎么组织管理的吧,最终我们可以知道这个用户是否具有某一个角色) 图书管理员 这个角色。则允许其访问books的这些操作。
3 以上两种方式的弊端在哪里?
其实不止以上两种,比如在Asp.Net Core中有内置的这些授权控制组件:

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
public IActionResult Login() => View(); public IActionResult Logout() => View();
}
以上这些本质上和上面的基于Scope和基于Role的属于同一种类型。我们这样做当然可以工作,但是问题来了,它们直观吗,灵活吗?繁琐吗?好用吗?能满足我们变化的需求吗?总有着一种把简单的事情搞复杂的感觉。比如现在我增需要增加一个角色,超级管理员,那么上述的代码是不是需要我们做出改变呢?
[HttpGet("")]
[Scope("book_manager")]
[Role("图书管理员","超级管理员")]
public Book[] Get() { return null; }
再比如,现在需要增加一个Scope book_reader ,它只能执行读取的操作,又要做出改变了吧。况且即使我们把Scope和Role合二为一了,还是混乱不堪。
4 基于权限为最小粒度的解决方案
那么造成这些问题的根本原因是什么?答:不管是Scope还是Role它们体现的都是一个隐式的描述信息,而不是某一个具体的操作行为的描述信息。既然我们知道了其症结所在,那么怎么解决这个问题呢?原理很简单,使用权限作为我们的最小单元,把Scope和Role等等还有其他的一些管理组织权限的概念都作为一个中间层,禁止它们出现在接口权限验证的地方,而是仅作为管理组织Permission的手段存在。然后改造上面的代码如下:
[Route("books")]
public class BooksController : Controller
{
[HttpGet("")]
[Permission("books.read")]
public Book[] Get() { return null; }
[HttpGet("{bookId}")]
[Permission("book.read")]
public Book Get(int bookId) { return null; }
[HttpPost("")]
[Permission("book.add")]
public Book Post(Book book) { return null; }
[HttpPut("{bookId}")]
[Permission("book.edit")]
public Book Put(int bookId, Book book) { return null; }
[HttpDelete("{bookId}")]
[Permission("book.delete")]
public Book Delete(int bookId) { return null; }
}
我们把每一个操作都定义一个权限Permission,不管你是Access Token的Scope,还是Role,都不会在这里出现。比如在检查超级管理员是不是能操作的时候,我们可以直接放行(把这些检查和我们对接口的操作权限的描述分开)。如果是名为book_reader的Scope的时候,我们让book_reader只关联books.read和book.read这两个Permission,而这种关联关系的管理,我们是可以通过数据存储来维持的,也很方便的提供管理页面来灵活的配置。而最终的代码上关心的只是Permission。这种方式可以称为Resource Based Access Control或者Permission Based Access Control。
5 Apache Shiro
以上是我自己的一些理解和思路,然后我发现了Apache Shiro这个项目,感觉就像是找到了组织,Apache Shiro走的更远,而且为Permission定义了一套规则。强烈建议读一读https://shiro.apache.org/permissions.html这篇文档。而.Net这边就没有这么好的福气了,,,Asp.Net Core中的默认授权过滤器还是传统的方式。

不过基于Asp.Net Core的Filter:IAuthorizationFilter,我们可以把这一整套授权控制方式给替换掉:使用代码:https://github.com/linianhui/oidc.example/tree/master/src/web.oauth2.resources;Filters代码:https://github.com/linianhui/oidc.example/tree/master/src/aspnetcore.filters.permissions。

从此和讨厌的 [Authorize(Roles ="图书管理员",Policy ="XXX")] 说再见。
以上只是个人的一些理解,如有错误,欢迎指正。
参考
强烈推荐:https://shiro.apache.org/permissions.html
https://stormpath.com/blog/new-rbac-resource-based-access-control
https://docs.microsoft.com/en-us/aspnet/core/security/authorization/
[认证授权] 6.Permission Based Access Control的更多相关文章
- Azure RBAC(Roles Based Access Control)正式上线了
期盼已久的Azure RBAC(Roles Based Access Control)正式上线了. 在非常多情况下.客户须要对各种类型的用户加以区分,以便做出适当的授权决定.基于角色的訪问控制 (RB ...
- Azure ARM (16) 基于角色的访问控制 (Role Based Access Control, RBAC) - 使用默认的Role
<Windows Azure Platform 系列文章目录> 今天上午刚刚和客户沟通过,趁热打铁写一篇Blog. 熟悉Microsoft Azure平台的读者都知道,在老的Classic ...
- Risk Adaptive Information Flow Based Access Control
Systems and methods are provided to manage risk associated with access to information within a given ...
- Custom Roles Based Access Control (RBAC) in ASP.NET MVC Applications - Part 1 (Framework Introduction)
https://www.codeproject.com/Articles/875547/Custom-Roles-Based-Access-Control-RBAC-in-ASP-NET Introd ...
- Azure ARM (17) 基于角色的访问控制 (Role Based Access Control, RBAC) - 自定义Role
<Windows Azure Platform 系列文章目录> 在上面一篇博客中,笔者介绍了如何在RBAC里面,设置默认的Role. 这里笔者将介绍如何使用自定的Role. 主要内容有: ...
- WebGoat系列实验Access Control Flaws
WebGoat系列实验Access Control Flaws Using an Access Control Matrix 在基于角色的访问控制策略中,每个角色都代表了一个访问权限的集合.一个用户可 ...
- Role-based access control modeling and auditing system
A role-based access control (RBAC) modeling and auditing system is described that enables a user to ...
- FreeIPA ACI (Access Control Instructions) 访问控制说明
目录 FreeIPA ACI (Access Control Instructions) 访问控制说明 一.ACI 位置 二.ACI 结构 三.ACI 局限性 四.复制拓扑中的ACI 五.操作ACI ...
- 【Spring Cloud & Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权
一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证授权.鉴权的逻辑,结合 ...
随机推荐
- 【quickhybrid】API多平台支撑的实现
前言 在框架规划时,就有提到过这个框架的一些常用功能需要支持H5环境下的调用,也就是需要实现API的多平台支撑 为什么要多平台支撑?核心仍然是复用代码,比如在微信下,在钉钉下,在quick容器下, 如 ...
- MyBatis简单使用
MyBatis MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使 ...
- 详解功能版本管理之使用eoLinker
先看一个对话: "这里,你改一下,这里返回一个object." "好...好......" "还有这里,返回个String." ...... ...
- JavaScript实现段落文本高亮
代码: <!doctype html> <html lang="en"> <head> <meta http-equiv="Co ...
- spark-submit参数说明--on YARN
示例: spark-submit [--option value] <application jar> [application arguments] 参数名称 含义 --master M ...
- 《Head First 设计模式》【PDF】下载
<Head First 设计模式>[PDF]下载链接: https://u253469.ctfile.com/fs/253469-231196307 First 设计模式>[PDF] ...
- Spark源码剖析(一):如何将spark源码导入到IDEA中
由于近期准备深入研究一下Spark的核心源码,所以开了这一系列用来记录自己研究spark源码的过程! 想要读源码,那么第一步肯定导入spark源码啦(笔者使用的是IntelliJ IDEA),在网上找 ...
- Find all factorial numbers less than or equal to N
A number N is called a factorial number if it is the factorial of a positive integer. For example, t ...
- vue2.0路由变化1
路由的步骤 1.定义组件 var Home={ template:'<h3>我是主页</h3>' }; var News={ template:'<h3>我是新闻& ...
- sql经典试题
1.一道SQL语句面试题,关于group by表内容:2005-05-09 胜2005-05-09 胜2005-05-09 负2005-05-09 负2005-05-10 胜2005-05-10 负2 ...