在前面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/

强烈推荐: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的更多相关文章

  1. Azure RBAC(Roles Based Access Control)正式上线了

    期盼已久的Azure RBAC(Roles Based Access Control)正式上线了. 在非常多情况下.客户须要对各种类型的用户加以区分,以便做出适当的授权决定.基于角色的訪问控制 (RB ...

  2. Azure ARM (16) 基于角色的访问控制 (Role Based Access Control, RBAC) - 使用默认的Role

    <Windows Azure Platform 系列文章目录> 今天上午刚刚和客户沟通过,趁热打铁写一篇Blog. 熟悉Microsoft Azure平台的读者都知道,在老的Classic ...

  3. Risk Adaptive Information Flow Based Access Control

    Systems and methods are provided to manage risk associated with access to information within a given ...

  4. 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 ...

  5. Azure ARM (17) 基于角色的访问控制 (Role Based Access Control, RBAC) - 自定义Role

    <Windows Azure Platform 系列文章目录> 在上面一篇博客中,笔者介绍了如何在RBAC里面,设置默认的Role. 这里笔者将介绍如何使用自定的Role. 主要内容有: ...

  6. WebGoat系列实验Access Control Flaws

    WebGoat系列实验Access Control Flaws Using an Access Control Matrix 在基于角色的访问控制策略中,每个角色都代表了一个访问权限的集合.一个用户可 ...

  7. 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  ...

  8. FreeIPA ACI (Access Control Instructions) 访问控制说明

    目录 FreeIPA ACI (Access Control Instructions) 访问控制说明 一.ACI 位置 二.ACI 结构 三.ACI 局限性 四.复制拓扑中的ACI 五.操作ACI ...

  9. 【Spring Cloud & Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权

    一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证授权.鉴权的逻辑,结合 ...

随机推荐

  1. 负载均衡之基于L7负载

    L7负载平衡 还有一种较为经常使用的负载平衡解决方式则是L7负载平衡.顾名思义,其主要通过OSI模型中的第七层应用层中的数据决定怎样分发负载. 在执行时.L7负载平衡server上的操作系统会将接收到 ...

  2. 参照企业微信审批业务,在Winform开发框架中工作流模块的实现业务审批

    目前微信的企业号已经切换到企业微信里面,这个是一个APP程序,提供了很丰富的企业应用,其中包括了业务审批处理,审批业务包括请假.报销.费用.出差等很多个审批场景,在Winform开发框架中工作流模块这 ...

  3. Druid数据库连接池源码分析

    上一篇文章重点介绍了一下Java的Future模式,最后意淫了一个数据库连接池的场景.本想通过Future模式来防止,当多个线程同时获取数据库连接时各自都生成一个,造成资源浪费.但是忽略了一个根本的功 ...

  4. angularf封装echarts

    前言:angular中快速使用echarts 在html使用ehart很简单,你只需要引入文件和按照官方例子按照对应参数配置和数据填充就Ok了,那么在angular中怎么使用eharts(可以使用ec ...

  5. Chef 自动化运维:开始“烹饪”

    在 Chef Workstation 上创建了一个 cookbook 之后,我们执行以下命令来进行测试: chef-client --local-mode --override-runlist fir ...

  6. 【python】函数说明文档

  7. webpack 3.X学习之多页面打包

    简介 我们开发不可能只写一个页面,每次都要写很多页面,这时为了开发效率,我们使用前端自动化工具webpack,那么webpack是如何打包页面的呢?又是如何打包多页面的呢? 单页面打包 我们知道要打包 ...

  8. 【Zookeeper】源码分析之服务器(三)之LeaderZooKeeperServer

    一.前言 前面分析了ZooKeeperServer源码,由于QuorumZooKeeperServer的源码相对简单,于是直接分析LeaderZooKeeperServer. 二.LeaderZooK ...

  9. Hibernate问题浅析

      1.什么是SessionFactory?什么是Session?httpsession和hibernate的session的有什么区别?     SessionFactory接口负责初始化Hiber ...

  10. ArcGIS API for JavaScript 4.2学习笔记[31] (补充学习)Task类

    Task这个东西很有用,是AJS中用于解决各种乱七八糟任务的一个类.它有很多子类,有用于空间分析的,有用于空间查询的,等等. 这篇作为补充学习的第一篇,也是进阶学习的第一篇,我就改个写法. 我将使用思 ...