受保护的 Web API:验证范围和应用角色Protected web API: Verify scopes and app roles

本文介绍如何将授权添加到 Web API。This article describes how you can add authorization to your web API. 这种保护可确保只有以下对象才能调用 API:This protection ensures that the API is called only by:

  • 代表具有适当范围的用户的应用程序。Applications on behalf of users who have the right scopes.
  • 具有适当应用程序角色的守护程序应用。Daemon apps that have the right application roles.

备注

本文中的代码片段摘自 GitHub 上的以下代码示例:The code snippets in this article are extracted from the following code samples on GitHub:

若要保护 ASP.NET 或 ASP.NET Core Web API,必须在下列其中一项中添加 [Authorize] 属性:To protect an ASP.NET or ASP.NET Core web API, you must add the [Authorize] attribute to one of the following items:

  • 控制器本身(若要保护所有控制器操作)The controller itself if you want all controller actions to be protected
  • API 的单个控制器操作The individual controller action for your API
    [Authorize]
    public class TodoListController : Controller
    {
     ...
    }

但是,这种保护并不足够。But this protection isn't enough. 它只能保证 ASP.NET 和 ASP.NET Core 对该令牌进行验证。It guarantees only that ASP.NET and ASP.NET Core validate the token. 你的 API 需要验证用来调用 API 的令牌是否是使用预期的声明请求的。Your API needs to verify that the token used to call the API is requested with the expected claims. 具体而言,这些声明需要验证:These claims in particular need verification:

  • 作用域(如果代表用户调用 API)。 The scopes if the API is called on behalf of a user.
  • 应用角色(如果可从守护程序应用调用 API)。 The app roles if the API can be called from a daemon app.

在代表用户调用的 API 中验证作用域Verify scopes in APIs called on behalf of users

如果某个客户端应用代表用户调用了你的 API,则该 API 需要请求具有该 API 的特定作用域的持有者令牌。If a client app calls your API on behalf of a user, the API needs to request a bearer token that has specific scopes for the API. 有关详细信息,请参阅代码配置 | 持有者令牌For more information, see Code configuration | Bearer token.

.NET Core.NET Core

验证每个控制器操作的范围Verify the scopes on each controller action

[Authorize]
public class TodoListController : Controller
{
    /// <summary>
    /// The web API will accept only tokens 1) for users, 2) that have the `access_as_user` scope for
    /// this API.
    /// </summary>
    static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };

    // GET: api/values
    [HttpGet]
    public IEnumerable<TodoItem> Get()
    {
         HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
        // Do the work and return the result.
        // ...
    }
...
}

VerifyUserHasAnyAcceptedScope 方法将执行诸如以下步骤的某些操作:The VerifyUserHasAnyAcceptedScope method does something like the following steps:

  • 验证是否存在名为 http://schemas.microsoft.com/identity/claims/scopescp 的声明。Verify there's a claim named http://schemas.microsoft.com/identity/claims/scope or scp.
  • 验证该声明的值是否包含 API 预期的作用域。Verify the claim has a value that contains the scope expected by the API.

更全面地验证范围Verify the scopes more globally

建议的方法是为 Web API 定义粒度范围并验证每个控制器操作中的范围。Defining granular scopes for your web API and verifying the scopes in each controller action is the recommended approach. 但是,也可以使用 ASP.NET Core 在应用程序或控制器级别验证范围。However, it's also possible to verify the scopes at the level of the application or a controller by using ASP.NET Core. 有关详细信息,请参阅 ASP.NET 核心文档中的基于声明的授权For details, see Claim-based authorization in the ASP.NET core documentation.

.NET MVC.NET MVC

对于 ASP.NET,只需将 HttpContext.User 替换为 ClaimsPrincipal.Current,并将声明类型 "http://schemas.microsoft.com/identity/claims/scope" 替换为 "scp"For ASP.NET, just replace HttpContext.User with ClaimsPrincipal.Current, and replace the claim type "http://schemas.microsoft.com/identity/claims/scope" with "scp". 另请参阅本文下文中的代码片段。Also see the code snippet later in this article.

验证守护程序应用调用的 API 中的应用角色Verify app roles in APIs called by daemon apps

如果 Web API 由某个守护程序应用调用,该应用应该对该 Web API 拥有应用程序权限。If your web API is called by a daemon app, that app should require an application permission to your web API. 公开应用程序权限(应用角色)中所示,你的 API 将公开此类权限。As shown in Exposing application permissions (app roles), your API exposes such permissions. 一个示例是 access_as_application 应用角色。One example is the access_as_application app role.

现在,你需要让 API 验证它收到的令牌是否包含 roles 声明,以及此声明是否具有预期的值。You now need to have your API verify that the token it receives contains the roles claim and that this claim has the expected value. 验证代码类似于对委托权限进行验证的代码,不同之处在于,你的控制器操作针对角色进行测试,而非针对作用域进行测试:The verification code is similar to the code that verifies delegated permissions, except that your controller action tests for roles instead of scopes:

ASP.NET CoreASP.NET Core

[Authorize]
public class TodoListController : ApiController
{
    public IEnumerable<TodoItem> Get()
    {
        HttpContext.ValidateAppRole("access_as_application");
        ...
    }

ValidateAppRole 方法是在 RolesRequiredHttpContextExtensions.cs 中的 Microsoft.Identity.Web 定义的。The ValidateAppRole method is defined in Microsoft.Identity.Web in RolesRequiredHttpContextExtensions.cs.

ASP.NET MVCASP.NET MVC

private void ValidateAppRole(string appRole)
{
    //
    // The `role` claim tells you what permissions the client application has in the service.
    // In this case, we look for a `role` value of `access_as_application`.
    //
    Claim roleClaim = ClaimsPrincipal.Current.FindFirst("roles");
    if (roleClaim == null || !roleClaim.Value.Split(' ').Contains(appRole))
    {
        throw new HttpResponseException(new HttpResponseMessage
        { StatusCode = HttpStatusCode.Unauthorized,
            ReasonPhrase = $"The 'roles' claim does not contain '{appRole}' or was not found"
        });
    }
}
}

当 Web API 只能由守护程序应用调用时接受仅限应用的令牌Accepting app-only tokens if the web API should be called only by daemon apps

用户还可以在用户分配模式下使用角色声明,如如操作指南:在应用程序中添加应用角色并在令牌中接收它们中所示。Users can also use roles claims in user assignment patterns, as shown in How to: Add app roles in your application and receive them in the token. 如果角色可同时分配给用户和应用,只需选中相应的角色就能让应用以用户身份登录,以及让用户以应用身份登录。If the roles are assignable to both, checking roles will let apps sign in as users and users to sign in as apps. 建议为用户和应用声明不同的角色,以避免出现这种混淆。We recommend that you declare different roles for users and apps to prevent this confusion.

如果你希望只有守护程序应用能够调用 Web API,请在验证应用角色时添加一个条件,规定该令牌是仅限应用的令牌。If you want only daemon apps to call your web API, add the condition that the token is an app-only token when you validate the app role.

string oid = ClaimsPrincipal.Current.FindFirst("oid")?.Value;
string sub = ClaimsPrincipal.Current.FindFirst("sub")?.Value;
bool isAppOnlyToken = oid == sub;

选中反向条件将只允许用于将用户登录的应用调用你的 API。Checking the inverse condition allows only apps that sign in a user to call your API.

后续步骤Next steps

转到此方案中的下一篇文章:转向生产Move on to the next article in this scenario, Move to production.