基于角色的ASP.NET Web APIs的授权

224 阅读18分钟

基于角色的访问控制,也被称为RBAC,是限制对组织内受保护资源访问的最常见的策略之一。它通过对用户进行角色分类,简化了权限分配。然而,许多第一次接触RBAC的人都有疑问。

  • 你如何在你的应用程序上反映这种用户分类以检查用户的权限?
  • 你如何在你的代码中处理角色?
  • 角色控制适合于Web APIs吗?

在这篇文章中,你将学习通过使用Auth0的功能在你的ASP.NET Core Web API中实现RBAC的最佳方法。

什么是RBAC?

考虑一个公司的薪资系统。访问这个系统的不同人有不同的权利来查看和改变数据。每个员工可以查看自己的数据,而人力资源部门的人可以查看所有员工的数据。另外,虽然员工只能查看他们自己的数据,但人力资源部门的人可以改变这些数据。角色也可能不只是按部门分配。例如,人力资源部门可能只有几个人应该有能力向工资系统添加新员工,而其余的人只有更新员工的权限。

为每个员工单独分配特定的权限可能是一个挑战,并导致错误。为了扩展薪资系统的例子,考虑到我们至少有4种权限。其中每一种都必须完全准确地分配给你公司的每个员工。 如果只有少数几个员工,每月有几个人力资源事件,这将很快演变成一个耗时和容易出错的活动。这还没有考虑到由于安全政策的改变而需要为一大群人重新分配权限。

基于角色的访问控制通过引入角色的概念来帮助进行权限分配。一个角色是一个权限的集合。基本上,你建立一个预定义的权限集,给它一个名字,如雇员人力资源助理人力资源经理,并将该角色分配给一个用户。如果你需要在所有拥有特定角色的用户中添加或删除一个权限,你只需要在他们被分配的角色中添加或删除该权限。这是一个很大的改进!

设置示例应用程序

为了最好地展示如何在与Auth0集成的ASP.NET Web API应用程序中使用角色,你将建立在一个示例应用程序上。我提供了一个起点,你可以通过在终端窗口运行以下命令来下载。

git clone --branch starter --single-branch https://github.com/auth0-blog/glossary-rbac-web-api-aspnet.git

请确保你的机器上已经安装了最新的.NET SDK,以运行样本的ASP.NET应用程序。

Web API为术语表实现了一个CRUD接口。它有一些基本的端点,允许你创建一个定义,读取一个术语定义列表或一个单一的定义,并更新或删除它们。要了解更多关于构建和保护这个应用程序的细节,请查看这篇博文

一旦你在你的机器上下载了该应用程序,请在Auth0上注册,以便启用对授权的支持。如果你还没有一个Auth0账户,你可以注册一个免费账户。按照本文的说明,向Auth0注册应用程序

在你完成注册后,移动到 glossary-rbac-web-api-aspnet文件夹,并打开 appsettings.json配置文件。它的内容应该是这样的。

// appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Auth0": {
    "Domain": "YOUR_AUTH0_DOMAIN",
    "Audience": "YOUR_UNIQUE_IDENTIFIER"
  }
}

YOUR_AUTH0_DOMAIN占位符替换为你的Auth0域和 YOUR_UNIQUE_IDENTIFIER占位符替换为你提供的作为你的API的唯一标识符的值(https://glossary.com,如果你保留了上述文章中建议的值)。

注意:你的Auth0域是一个字符串,格式为 YOUR-TENANT-NAME.auth0.com其中 YOUR-TENANT-NAME是你在Auth0创建账户时提供的名字。欲了解更多信息,请查看文档

为了测试一切是否如预期般运作,在终端窗口中输入dotnet run ,启动ASP.NET Web API应用程序。现在将你的浏览器指向 https://localhost:5001/swagger你应该看到如下所示的Swagger用户界面,以交互方式测试API。

Swagger UI for the Glossary API

要调用POST、PUT和DELETE动作,你需要一个访问令牌,如本节所解释。相关的端点已经需要一个访问令牌,但它们并不对授予用户的权限进行任何检查。这意味着任何被授权访问受保护的端点的用户可以做任何事情:创建一个新的术语,修改一个现有的术语,以及删除它们。

在下面的章节中,你将实现RBAC,只允许特定的用户执行特定的操作。

为你的API定义权限

作为对访问你的Web API进行更精细控制的第一步,你必须为你的应用程序定义所需的权限。在词汇表网络API应用程序的情况下,使用以下字符串定义权限。

  • create:term.这个权限允许你在词汇表中添加一个新的术语。
  • update:term.有了这个权限,你可以更新一个现有的术语。
  • delete:term.该权限允许你从词汇表中删除一个术语。

要把这些权限与API联系起来,请到Auth0仪表板的API部分,选择你为词汇表应用程序定义的API。然后选择*"*权限 "选项卡。在这里,你可以添加权限和上面定义的描述。在这个过程结束时,该页面将看起来像下面这样。

Defining permissions in the Auth0 Dashboard

这个配置通知Auth0,词汇表的网络API支持这些权限。

接下来,移动到设置标签,向下滚动到RBAC设置部分。在这里,点击启用RBAC在访问标记中添加权限的切换按钮,如下图所示,然后点击底部的保存按钮。

Enabling RBAC

这些设置将在用户登录时强制评估角色和权限分配。

Auth0和RBAC

Auth0让你通过它的仪表板轻松地组织你的RBAC。如果你有更复杂或具体的需求,你也可以通过使用Auth0管理API来管理用户和角色。在这篇文章中,你将使用仪表板来管理用户和角色。

你的目标是创建角色,以便只有特定的用户可以执行特定的动作。在实践中,你必须创建。

  • 编辑角色,包括创建 (create:term) 和更新 (update:term) 词汇表术语。
  • 管理员角色,包括删除( )术语的权限。delete:term)词汇表的权限,除了可以创建和修改该词汇表外。

创建角色

考虑到这个目标,前往Auth0仪表板,在侧边菜单的用户管理部分选择角色。点击右上角的创建角色按钮,并在表格中填写以下数据。

  • glossary-admin作为名称。
  • 词汇管理员作为描述。

现在点击 "创建"按钮。在你得到的屏幕上,选择 "权限"选项卡,通过点击 "添加权限 "按钮,将所需的权限与定义该角色联系起来。你需要选择你用Auth0注册的API*(词汇表*,如果你保留建议的名称),并选择它的所有权限,如下图所示。

Assign permissions to the admin role

最后,单击 "添加权限"(Add Permissions)按钮。你刚刚创建了 glossary-admin角色作为一个集合,其中包括 create:term, update:termdelete:term权限的集合

现在,重复同样的过程来创建编辑器角色,数据如下。

  • glossary-editor作为名称。
  • 词汇表编辑器作为描述。

这一次,你将只添加 create:termupdate:term权限,如下图所示。

Assigning permissions to the editor role

记得点击添加权限按钮,最终完成角色的创建。 glossary-editor角色。

用角色创建用户

归根结底,没有用户,角色是毫无意义的。现在,你将创建两个用户,并给他们分配你所做的角色,以测试该应用程序。

第一个用户将拥有 john@example.com作为他们的电子邮件和用户名,并将被分配到 glossary-editor角色。

第二个用户将有 jane@example.com作为他们的电子邮件和用户名,并将被分配到 glossary-admin角色。

在你的Auth0仪表板上打开用户部分,点击创建用户按钮。然后,在表格中填写第一个用户的所需数据。john@example.com作为电子邮件,一个你选择的密码,和 Username-Password-Authentication作为连接。下面是一个填写表格的例子。

creating-user-auth0-dashboard

点击 "创建"按钮来创建用户。

现在,在用户页面上,选择 "角色"标签,点击 "分配角色"按钮。在弹出的窗口中,选择 glossary-editor从下拉列表中选择角色,然后点击分配按钮。窗口看起来应该如下。

Assign editor role to the user

重复到目前为止描述的相同步骤,创建用户 jane@example.com并给他们分配 glossary-admin角色。

检查你的API中的权限

在这一点上,你已经配置了你的API,并在Auth0上用各自的角色创建了用户。现在是时候让你的应用程序对它收到的HTTP请求有更细化的控制了。

回顾一下,角色是权限的集合。当你在Auth0仪表板中为你的API启用RBAC时,你确保了Auth0为API发出的令牌将包括访问令牌中的权限。因此,当客户端使用相同的令牌进行HTTP请求时,API可以解析令牌的权限。

为了正确授权传入的HTTP请求,你的API需要确保访问令牌具有必要的权限。你可以通过定义一个或多个授权策略并将其应用于实现你的ASP.NET Web API的方法来完成这项任务。

授权策略是一组用户或应用程序必须满足的要求,以便被允许在资源上执行动作。让我们看看在实践中如何定义和应用授权策略。

要为词汇表Web API定义一个授权策略,请打开项目根目录下的 Startup.cs文件,并将以下代码附加到 ConfigureServices()方法的主体。

// Startup.cs

// ...existing code...

namespace Glossary
{
  public class Startup
  {
    // ...existing code...
    
    public void ConfigureServices(IServiceCollection services)
    {
      // ...existing code...
      
      //👇 new code
      services.AddAuthorization(options =>
      {
        options.AddPolicy("CreateAccess", policy => 
                          policy.RequireClaim("permissions", "create:term"));
        options.AddPolicy("UpdateAccess", policy => 
                          policy.RequireClaim("permissions", "update:term"));
        options.AddPolicy("DeleteAccess", policy => 
                          policy.RequireClaim("permissions", "delete:term"));
      });
      //👆 new code
    }
    
    // ...existing code...
  }
}

在这里,你已经调用了 AddAuthorization()方法来配置ASP.NET授权服务。你还通过 AddPolicy()``options 参数的方法定义了三个授权策略。每个策略都有一个名称和一个lambda表达式。例如,CreateAccess 授权策略是通过要求访问令牌中的permissions 要求来定义的,其值必须是 create:term.同样,对于UpdateAccessDeleteAccess 策略也是如此。

在ASP.NET中存在不同的方法来创建授权策略。例如,你可以使用基于请求的授权基于策略的授权方法。你甚至可以通过定制内置的ASP.NET授权中间件来创建自己的方法。

应用一个授权策略

一旦你定义并注册了你的授权策略,你就可以在你的Web API中应用它们。应用授权策略仅仅是向Authorize 属性传递参数的问题。因此,打开 GlossaryController.cs``Controllers 文件,并应用下面指出的修改。

// Controllers/GlossaryController.cs

// ...existing code...

namespace Glossary.Controllers
{
  [ApiController]
  [Route("api/[controller]")]
  public class GlossaryController : ControllerBase
  {
    // ...existing code...

    [HttpPost]
    [Authorize(Policy = "CreateAccess")]    //👈 changed code
    public ActionResult Post(GlossaryItem glossaryItem)
    {
      // ...existing code...
    }

    [HttpPut]
    [Authorize(Policy = "UpdateAccess")]    //👈 changed code
    public ActionResult Put(GlossaryItem glossaryItem)
    {
      // ...existing code...
    }

    [HttpDelete]
    [Route("{term}")]
    [Authorize(Policy = "DeleteAccess")]    //👈 changed code
    public ActionResult Delete(string term)
    {
      // ...existing code...
    }
  }
}

这样一来,你就要求ASP.NET授权中间件评估一个特定的策略来授权执行相关控制器的方法。

你的Web API已经准备好运行和检查权限了!

测试Web API

为了测试你的词汇表Web API,你需要一个客户端,让你的用户通过Auth0进行认证,并根据他们的角色获得一个具有适当权限的访问令牌。幸运的是,样本Web API的Swagger用户界面就是为了成为你的API的一个实际客户端。你只需要启用OAuth支持,这样用户就可以进行认证并获得他们的访问令牌。

配置客户端

要配置你的Swagger用户界面,你需要向Auth0注册。前往你的Auth0仪表板的应用程序部分,点击右上角的创建应用程序按钮。在出现的窗口中,提供一个有意义的名字,例如,词汇表客户端,并选择常规Web应用程序作为应用程序类型。该窗口应该如下图所示。

Register the glossary client with Auth0

现在,点击创建按钮进行确认。客户端页面创建后,移到 "设置"选项卡,注意客户端ID和客户端秘密的值。

现在,回到你的项目中,打开 appsettings.json配置文件,并应用下面强调的变化。

// appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Auth0": {
    "Domain": "YOUR_AUTH0_DOMAIN",
    "Audience": "YOUR_UNIQUE_IDENTIFIER",
    // 👇 new keys
    "ClientId": "YOUR_CLIENT_ID",
    "ClientSecret": "YOUR_CLIENT_SECRET"
    // 👆 new keys
  },
  "SwaggerUISecurityMode": "oauth2"        // 👈 new key
}

你在Auth0 部分内添加了ClientIdClientSecret 键。替换掉 YOUR_CLIENT_IDYOUR_CLIENT_SECRET占位符替换为你刚刚从Auth0仪表板上注释的相应值。DomainAudience 键保持你在本文开头已经分配的值。

另外,你还添加了带有oauth2 值的SwaggerUISecurityMode 密钥。这个密钥使Swagger UI能够作为一个OAuth客户端,这样它就可以代表用户向Auth0请求访问令牌。稍后你会看到它是如何工作的。

请记住,SwaggerUISecurityMode 密钥是这个样本项目所支持的一个自定义密钥。它只是为了演示而需要,以便用Swagger UI测试你的API。在真实世界的情况下,你的API将被真正的客户所调用。

现在,通过在终端窗口中输入dotnet run ,并将你的浏览器指向https://localhost:5001/swagger,来启动Web API。

我建议在隐身模式下使用你的浏览器,以便在用户之间轻松切换。 事实上,Swagger UI中一个尚未解决的问题使你无法从授权服务器上注销。更多信息请参见这些 问题

测试使用编辑器角色的访问

在你的Web API的Swagger用户界面中,点击授权按钮。你应该看到一个像下面这样的窗口。

Swagger UI authorization window

这个窗口要求授权Swagger UI客户端代表用户获得一个访问令牌。通过点击这里的授权按钮,你将被重定向到Auth0通用登录页面。在该页面上,通过提供以下凭证,以拥有编辑角色的用户身份进行认证 john@example.com,因为你之前已经创建了它们。认证后,你将被重定向到以下屏幕。

Swagger UI authorized window

你的Swagger UI客户端现在已经收到了该用户的访问令牌。点击关闭按钮,并尝试通过POST动作向词汇表添加一个新术语。在Swagger UI中,点击POST /api/Glossary行,然后点击Try itout按钮。现在,按照下面的例子添加一个新的术语定义。

Create a new glossary term

通过点击执行按钮,你应该得到一个类似于以下的成功响应。

Successful response to creating a new term

如果你从curl 语句文本框中复制访问令牌,并在jwt.io中解码,你应该看到一个类似以下的有效载荷。

Editor access token payload

你可以看到permissions ,其中包含 create:termupdate:term权限。这些权限是由于他们的编辑角色而分配给当前用户的。此外,该 create:term权限是CreateAccess 策略所要求的,该策略控制对你的API控制器中的 Post()方法的权限。所以,一切都按预期进行。

如果你试图通过PUT动作修改一个词汇表术语,你也应该得到一个成功的响应。

用管理员角色测试API

然而,如果你试图删除新创建的术语,你应该收到一个 403 Forbidden状态代码。事实上,你的访问令牌并不包含这个 delete:term权限。只有具有管理员角色的用户才被允许删除术语。

所以,清除你浏览器中的cookies(或者关闭浏览器并在隐身模式下重新打开)。

再次点击 "授权"按钮,这次要以用户身份进行认证。 jane@example.com用户。该用户具有管理员角色。现在,尝试执行所有三个受保护的动作。POST, PUT, 和 DELETE。你应该能够成功执行它们。

角色和API

将RBAC应用于上文所示的Web API,利用了Auth0的能力,即在登录时从用户角色中提取权限,并将其添加到permissions 访问标记的要求中。作为API开发者,这种方法让你只专注于权限,而把角色管理留给Auth0。

作为一个ASP.NET开发者,你可能知道你可以使用Roles 参数和Authorize 属性,如下面的例子中所示。

[HttpPost]
[Authorize(Roles = "glossary-editor, glossary-admin")]
public ActionResult Post(GlossaryItem glossaryItem)
{
  // ... existing code ...
}

所以,你可能会在这个时候问自己几个问题。

  • 是否可以在Auth0发出的访问令牌中包括用户角色?
  • 为什么不在Web API方面检查角色而不是权限?

让我们试着回答这两个问题。

在访问令牌中包括角色

Auth0允许你在登录时发出的访问令牌中添加用户角色。然而,这不是一个内置的功能,也就是说,你不需要任何明确的干预就能得到的东西。为了用用户的角色来充实访问令牌,你需要在登录流程中添加一个Auth0动作。Action是一个JavaScript函数,允许你定制不同Auth0流程的标准行为。请看这篇博文,了解Auth0动作的概况

展示如何使用Auth0动作在访问令牌中添加用户角色不在本文的范围之内。然而,你可以看一下这个Action的例子来了解它是如何工作的。

检查权限或角色?

你可能想检查角色而不是Web API方面的权限,其中一个原因可能是它们易于阅读和快速理解。它们导致了用户角色和你的API之间的直接映射。它们很容易阅读和理解。看一下下面的代码,你就能一目了然地知道谁被允许做某个特定的动作。

[HttpDelete]
[Route("{term}")]
[Authorize(Roles = "glossary-admin")]
public ActionResult Delete(string term)
{
  // ... existing code ...
}

很明显,只有词汇表管理员可以删除一个词汇表术语。拥有权限而不是角色需要你考虑谁是拥有权限的用户。 delete:term权限的用户可能是谁。这意味着你在角色和权限之间有一个额外的映射层。

然而,在Web API方面检查角色以换取这种好处是有一些缺点的。

想象一下,你用角色检查而不是权限检查来实现你的词汇表网络API。在你的Web API发布后,你需要为你的词汇表应用程序提供一个新的角色:助理角色。假设你把这个角色命名为 glossary-assistant.这个角色的用户只能更新现有的术语,所以他们只拥有 update:term权限。

为了在你的Web API上启用这个角色,你现在需要改变它在控制器中的代码。你需要把这个 glossary-assistant角色到方法的允许角色列表中。 Put()方法。这并不复杂...但是现在,你需要用新的代码重新部署你的API!

如果你检查权限而不是角色,你就不需要在Web API侧进行任何更新。角色管理和权限解包是Auth0的责任。

再次强调:角色只是权限的集合。一般来说,权限是相当静态的,而角色是更动态的。需要为新的角色分组权限,或者为同一个用户组合多个角色,这使得权限检查更加方便和低维护性。

想想简单的词汇表网络API。它只依赖于三个权限。然而,它可以导致七个角色的创建(如果我们也考虑到空角色,则是八个)。这应该让你考虑到在Web API方面处理角色的机会。

最后,考虑到Auth0允许你直接给用户分配权限。在这种情况下,如果你只检查角色,你会错过任何直接分配的权限,导致你的Web API出现安全漏洞。

总结

在这篇文章中,你学到了如何在你的ASP.NET Web API中使用Auth0基于角色的访问控制。

你首先定义并分配了用户的角色。然后,你用Auth0注册了一个词汇表Web API并启用了RBAC支持。接下来,你在你的Web API中实现了一个授权策略来检查用户的权限。

最后,你了解到为什么在API端检查权限比直接检查角色更好。

你可以在这个GitHub仓库中找到文章中实现的词汇表Web API的最终版本。