在前面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. LINUX:alias命令详解

    发现目前安装的g++并没有开启选项 -std=c++11,无法使用c++11的新标准及其中的列表初始化.搜索后得到解决方法:键入:alias  g++="g++ -std=c++11&quo ...

  2. iOS 去掉小数点后边多余的0

    -(NSString*)removeFloatAllZero:(NSString*)string { NSString * testNumber = string; NSString * outNum ...

  3. C# 内存模型

    C# 内存模型 This is the first of a two-part series that will tell the long story of the C# memory model. ...

  4. CSS3 使用选择器在页面插入内容

    使用选择器来插入文字 h2:before{ content:'COLUMN'; color:white: background-color:orange: padding:1px 5px; } 注意点 ...

  5. Locust no-web 模式与参数详解

    读前参考:<性能测试工具Locust > 熟悉 Apache ab 工具的同学都知道,它是没有界面的,通过命令行执行. Locust 同样也提供的命令行运行,好处就是更节省客户端资源. 命 ...

  6. Golang丰富的I/O----用N种Hello World展示

    h1 { margin-top: 0.6cm; margin-bottom: 0.58cm; direction: ltr; color: #000000; line-height: 200%; te ...

  7. base64格式图片转换为FormData对象进行上传

    原理:理由ArrayBuffer.Blob和FormData var base64String = /*base64图片串*/; //这里对base64串进行操作,去掉url头,并转换为byte va ...

  8. mac安全权限解决

    如果有以下提示的,并不是文件损坏了,而是macOS Sierra新系统取消了安装本地程序的功能.   解决办法如下: 1.首先打开终端(找不到哪里打开终端 command+空格 搜索 "终端 ...

  9. Life in Changsha College-第一次冲刺

    第一次冲刺任务 基于大局的全面性功能框架定位,要求能实现用户基于自己的需求进行的一系列操作. 用户故事 用户打开"生活在长大"的界面 程序首页展示校园服务,论坛等相关信息 用户选择 ...

  10. java 泛型基础问题汇总

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Java语言引 ...