调用一个有访问令牌保护的API是非常容易的。这只是在HTTP请求中添加一个承载令牌的问题。在.NET中,你可以用不同的方式做到这一点。但在ASP.NET核心应用程序中,什么是最好的方法呢?让我们通过一个实际的例子来了解一下。
调用受保护的API。基础知识
在OAuth 2.0的背景下,应用程序可以通过在其HTTP请求中包含一个访问令牌来调用受保护的API(RFC6750)。在HTTP请求中包含访问令牌的最常见和推荐的方式是使用Bearer认证方案。简而言之,这意味着请求应包括一个带有Bearer 前缀的Authorization 头,如下面的例子所示。
GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM
Bearer 前缀后面的字符串是访问令牌的值。
请记住两件事:
Bearer前缀表明任何拥有该令牌的应用程序都可以使用它来调用受保护的API。API不要求进一步的身份证明。这意味着你必须通过安全通道(如HTTPS)发送请求,并采取极端谨慎的态度来防止任何令牌被盗的风险。- 令牌的值与客户端应用程序无关。它可以是JWT或使用其他格式。从调用应用程序的角度来看,它只是一串字符。
基本上,这就是你需要知道的调用一个受保护的API的全部内容:
-
如何获得一个访问令牌。
-
如何将令牌附加到HTTP请求中。
你将在接下来的章节中学习如何在实践中做到这一点。
示例程序
为了展示如何从ASP.NET Core中调用受保护的API,你将探索一个示例应用程序的代码,并逐步修改它,直到你得到最好的方法。
要运行这个示例程序,你需要在你的机器上安装.NET 6.0 SDK。
如果这是你第一次在你的机器上运行ASP.NET Core应用程序,请确保信任其HTTPS开发证书。
让我们通过在终端窗口运行以下命令,从GitHub下载示例应用程序。
git clone --branch starting-point --single-branch https://github.com/auth0-blog/call-protected-api-aspnet-core.git
你会得到一个 reward-points-redemption-app文件夹,其中有两个子文件夹:catalog 和 redemption-api.catalog 文件夹包含一个ASP.NET Core MVC应用程序的代码,该应用程序向认证的用户显示奖励目录。这个 redemption-api文件夹包含一个ASP.NET Core Web API的代码,允许用户兑换他们的积分以获得奖励。
下图显示了系统组件的整体互动。

按照该图。
- 用户用Auth0进行认证。
- 认证后,用户浏览奖励目录。
- 当用户想要兑换他们的积分以获得目录中的物品时,目录应用程序会向兑换API发出请求。
你从GitHub上下载的项目已经实现了这些交互的大部分内容。但缺少获取访问令牌和调用API的功能。你将在稍后实现这一功能。
虽然调用受保护的API的应用程序是一个ASP.NET Core MVC应用程序,但你在本文中学到的所有知识都适用于其他ASP.NET Core应用程序框架,如Razor Pages和Blazor Server。
配置目录应用程序
在继续进行所需的修改之前,你必须配置两个应用程序,使其与Auth0一起工作。让我们从目录应用程序开始。你需要一个Auth0账户。如果你还没有,你可以注册一个免费的。
在Auth0仪表板上,移动到应用程序部分,并遵循这些步骤:
- 单击 "创建应用程序"。
- 为你的应用程序提供一个友好的名称(例如,目录网络应用),并选择常规网络应用作为应用程序类型。
- 最后,单击 "创建"按钮。
这些步骤使Auth0知道你的ASP.NET Core MVC应用程序,并将允许你控制访问。
创建应用程序后,你登陆了应用程序页面的快速启动标签。忽略它,移到设置选项卡。在这里记下你的Auth0域和客户ID。然后,在同一表格中,将值分配给 https://localhost:7095/callback到允许的回调URLs字段,并将值 https://localhost:7095/到允许注销的URLs字段。
第一个值告诉Auth0在用户认证后要回调哪个URL。第二个值告诉Auth0,用户在注销后应该被重定向到哪个URL。
点击 "保存更改"按钮来应用它们。
现在,进入catalog 文件夹,打开 appsettings.json配置文件。其内容如下图所示。
// catalog/appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Auth0": {
"Domain": "YOUR_AUTH0_DOMAIN",
"ClientId": "YOUR_CLIENT_ID"
}
}
替换占位符 YOUR_AUTH0_DOMAIN和 YOUR_CLIENT_ID替换为基本信息部分的应用程序设置中的相应数值。
配置API
现在我们来配置赎回的API。转到Auth0仪表板的API部分,点击创建API按钮。然后为你的API提供一个友好的名字(例如:Redemption API)和一个URI格式的唯一标识符(例如:。 https://redemption-api.com).保留RS256作为签名算法,并点击创建按钮。
要完成API配置,请移动到 redemption-api文件夹,并打开 appsettings.json配置文件。其内容应如下。
//redemption-api/appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Auth0": {
"Domain": "YOUR_AUTH0_DOMAIN",
"Audience": "YOUR_UNIQUE_IDENTIFIER"
}
}
将 YOUR_AUTH0_DOMAIN占位符替换为你的Auth0域,也就是你为目录应用程序得到的那个域,而 YOUR_UNIQUE_IDENTIFIER占位符用你提供的作为你的API的唯一标识符的值来代替(如果你保留了上面建议的值)。https://redemption-api.com,如果你保留了上面建议的值)。
运行应用程序
现在,让我们运行应用程序,检查是否一切如预期的那样工作。
在这个 redemption-api文件夹中,运行以下命令。
dotnet run
几秒钟后,API应用程序应该运行并监听到 https://localhost:7151地址。你可以通过在终端窗口中运行以下命令来检查一切是否按预期运行。
curl https://localhost:7151/redeem -v
你应该得到一个类似于以下的响应。
* Trying 127.0.0.1:7151...
* Connected to localhost (127.0.0.1) port 7151 (#0)
# ...other messages omitted...
> GET /redeem HTTP/1.1
> Host: localhost:7151
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Content-Length: 0
< Date: Wed, 17 Aug 2022 09:41:50 GMT
< Server: Kestrel
< WWW-Authenticate: Bearer
<
* Connection #0 to host localhost left intact
注意你收到的 401 Unauthorized状态代码,因为你没有在你的请求中提供一个访问令牌。
现在,移动到catalog 文件夹,在一个新的终端窗口启动dotnet run 命令。几秒钟后,你应该得到目录应用程序监听到的 https://localhost:7095地址。
将你的浏览器指向该地址,你会得到以下页面。

通过点击页面右上角的登录链接,你会导航到Auth0通用登录页面,该页面允许你作为一个用户注册或登录。认证后,你就可以进入奖励目录,兑换你的积分,如下图所示。

请求访问令牌
让我们先处理一下如何获得访问令牌的问题。该样本应用程序使用Auth0 ASP.NET Core Authentication SDK,所以获得一个访问令牌是非常容易的。转到catalog 文件夹,打开 Program.cs文件。然后,应用下面强调的修改。
//catalog/Program.cs
using Auth0.AspNetCore.Authentication;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuth0WebAppAuthentication(options => {
options.Domain = builder.Configuration["Auth0:Domain"];
options.ClientId = builder.Configuration["Auth0:ClientId"];
// 👇 new code
options.ClientSecret = builder.Configuration["Auth0:ClientSecret"];
// 👆 new code
options.Scope = "openid profile email";
})
// 👇 new code
.WithAccessToken(options =>
{
options.Audience = builder.Configuration["Auth0:Audience"];
});
// 👆 new code
builder.Services.AddControllersWithViews();
// ...existing code...
第一个变化是在Auth0 SDK的配置中添加客户秘密,而第二个变化则是调用了 WithAccessToken()方法,传递你要调用的API的受众。
默认情况下,Auth0 ASP.NET Core AuthenticationSDK实现了Implicit Grant with Form Post,只获得ID令牌。为了同时获得访问令牌,你需要使用授权码授予,这需要使用客户端的秘密。
客户端秘密和听众的值都来自配置文件中的两个键。 appsettings.json配置文件中的两个键。因此,在catalog 文件夹中打开这个文件,添加如下所示的密钥。
// catalog/appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Auth0": {
"Domain": "YOUR_AUTH0_DOMAIN",
"ClientId": "YOUR_CLIENT_ID",
"ClientSecret": "YOUR_CLIENT_SECRET", // 👈 new key
"Audience": "YOUR_UNIQUE_IDENTIFIER" // 👈 new key
}
}
将 YOUR_CLIENT_SECRET占位符替换为你在Auth0仪表板上的应用程序的客户秘密。替换 YOUR_UNIQUE_IDENTIFIER占位符替换为你提供的作为赎回API的唯一标识符的值(https://redemption-api.com,如果你保留了前面建议的值)。
有了这些变化,当用户认证时,你的应用程序会同时收到ID和访问令牌。SDK在幕后处理这个过程,并将访问令牌存储在当前的HTTP上下文对象中。当你需要调用受保护的API时,你会用到它。
使用你的访问令牌
一旦你申请了你的访问令牌,让我们看看如何使用它来调用赎回API。
在深入研究之前,你必须知道,让用户触发调用并显示响应的所需代码已经全部到位。你只需要专注于对赎回API的调用。基本上,你的 "立即兑换!"按钮调用了 Catalog/Redeem动作,在你的ASP.NET Core MVC应用程序中。这个动作是由 Redeem()中实现的方法。 catalog/Controllers/CatalogController.cs文件中实现的方法来处理。目前,它显示如下。
// catalog/Controllers/CatalogController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using acme.Models;
namespace acme.Controllers;
public class CatalogController : Controller {
// ...existing code...
[Authorize]
public IActionResult Redeem(string prodid)
{
return View(new RedeemResponse() {Code = 0});
}
}
实际上,这段代码并没有什么特别的,除了显示在文件中定义的视图 catalog/Views/Catalog/Redeem.cshtml.让我们通过调用赎回API来使其具有操作性。
替换当前的 Redeem()中的方法。 CatalogController.cs文件中的方法,改为以下内容。
// catalog/Controllers/CatalogController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication; // 👈 new code
using System.Net.Http.Headers; // 👈 new code
using acme.Models;
namespace acme.Controllers;
public class CatalogController : Controller {
// ...existing code...
[Authorize]
public async Task<IActionResult> Redeem(string prodid)
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
RedeemResponse response;
using(var httpClient = new HttpClient())
{
string ApiUrl = "https://localhost:7151/redeem";
httpClient.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("Bearer", accessToken);
response = await httpClient.GetFromJsonAsync<RedeemResponse>(ApiUrl);
response.ProdId = prodid;
}
return View(response);
}
}
首先,你添加一个对 Microsoft.AspNetCore.Authentication和 System.Net.Http.Headers命名空间的引用,这些命名空间为你的代码提供了一些特定的功能。
这个新的 Redeem()方法有一个prodid 参数,代表你要赎回的目录项目。作为第一步,该方法通过调用 获得访问令牌。 GetTokenAsync()然后创建一个HttpClient 实例来进行HTTP请求。它建立了要调用的API端点的URL,并将访问令牌作为承载令牌添加到HTTP头文件中。最后,它调用API,获得响应,并将其传递给视图。这就是全部!
为了测试这些变化,请确保API应用程序已经启动并运行,并且你已经从目录应用程序中注销了。你需要再次认证以获得访问令牌。
一旦目录应用程序运行,进入目录页面并点击任何一个 "立即兑换!"按钮。如果成功的话,你应该得到一个像下面这样的屏幕。

否则,你会得到一个类似下面的信息。

记住,API会产生随机响应。
棒极了!现在你有了一个可以工作的ASP.NET Core应用程序,可以调用一个受保护的API!
不幸的是,这并不是调用API的最佳解决方案。目前的代码有几个问题。每次你需要调用API时,你都必须重写同样的代码。实际上,这很容易通过一些重构技术来克服。然而,每次调用API都要创建一个HttpClient 实例,这会导致套接字耗尽的问题。建议使用HttpClient 调用API的方法是使用IHttpClientFactory ,并定义一个自定义的授权消息处理器。
让我们在接下来的章节中看看如何应用这些变化。
使用IHttpClientFactory
由于你不应该在每次调用API时都创建一个HttpClient 实例,你可能会想到使用Singleton模式来解决这个问题。这可能是一个可以接受的解决方案,适用于短命的应用程序,如控制台应用程序。不幸的是,它并不适合长期运行的应用程序,如ASP.NET Core应用程序。你可能会遇到与DNS变化有关的问题。
如前所述,在你的ASP.NET Core应用程序中创建一个HttpClient 实例的最佳方法是通过IHttpClientFactory 。这个接口允许你通过依赖性注入来配置和创建HttpClient 实例。请查看此文档,了解更多关于使用IHttpClientFactory 的好处。
让我们开始吧,打开 Program.cs``catalog 文件,并添加下面强调的代码。
// catalog/Program.cs
using Auth0.AspNetCore.Authentication;
var builder = WebApplication.CreateBuilder(args);
// ...existing code...
builder.Services.AddControllersWithViews();
// 👇 new code
builder.Services.AddHttpClient("RedemptionAPI",
client => client.BaseAddress = new Uri(builder.Configuration["RedemptionApiBaseUrl"]));
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("RedemptionAPI"));
// 👆 new code
var app = builder.Build();
// ...existing code...
该 AddHttpClient()方法定义了一个名为HttpClient 的实例(RedemptionAPI ),并将API地址作为请求时使用的基本地址。实际的HttpClient 实例是由 CreateClient()``IHttpClientFactory 方法创建。创建HttpClient 实例的函数被定义为依赖系统中的一个范围服务。
正如你可能已经注意到的,API的基本地址值是从应用程序配置中获取的,而不是硬塞进代码中。因此,你需要在配置文件中添加一个新的RedemptionApiBaseUrl 。 appsettings.json配置文件,如下所示。
// catalog/appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Auth0": {
"Domain": "YOUR_AUTH0_DOMAIN",
"ClientId": "YOUR_CLIENT_ID",
"ClientSecret": "YOUR_CLIENT_SECRET",
"Audience": "YOUR_UNIQUE_IDENTIFIER"
},
"RedemptionApiBaseUrl": "https://localhost:7151" // 👈 new key
}
请注意,RedemptionApiBaseUrl 密钥没有被包括在Auth0 部分,因为它与Auth0设置无关。
创建一个授权信息处理程序
你在上一节添加的代码只是允许你正确地创建HttpClient 的实例。但是你需要把访问令牌添加到每个对API的HTTP请求中。你可以通过一个自定义的消息处理程序来做到这一点。
消息处理程序是一个组件,它可以在HTTP消息到达控制器或发送到客户端之前对其进行操作。查看此文档,了解更多关于ASP.NET Core中的消息处理程序。
要创建你的自定义消息处理程序,在 文件夹中创建一个名为 TokenHandler.cs``catalog 的文件,其中包含以下代码。
// catalog/TokenHandler.cs
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Authentication;
public class TokenHandler : DelegatingHandler {
private readonly IHttpContextAccessor _httpContextAccessor;
public TokenHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
return await base.SendAsync(request, cancellationToken);
}
}
这里定义的TokenHandler 类继承于DelegatingHandler 类。它通过依赖性注入获得IHttpContextAccessor 接口的实现。这允许你的代码访问当前的HTTP上下文。TokenHandler 类定义了 SendAsync()方法,它基本上是用访问令牌执行HTTP请求,就像你之前做的那样。
为了使消息处理程序对你的应用程序可用,你必须再次修改 Program.cs文件,再一次修改catalog 。这里强调了对代码的修改。
// catalog/Program.cs
using Auth0.AspNetCore.Authentication;
var builder = WebApplication.CreateBuilder(args);
// ...existing code...
builder.Services.AddControllersWithViews();
// 👇 new code
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<TokenHandler>();
// 👆 new code
builder.Services.AddHttpClient("RedemptionAPI",
client => client.BaseAddress = new Uri(builder.Configuration["RedemptionApiBaseUrl"]))
.AddHttpMessageHandler<TokenHandler>(); // 👈 new code
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("RedemptionAPI"));
var app = builder.Build();
// ...existing code...
你添加了IHttpContextAccessor 服务的默认实现。你还将TokenHandler 类添加到应用服务中,并将其作为消息处理程序附加到HttpClient 实例中。
你几乎已经完成了!你只需要使用这个基础设施来实际调用API。
调用受保护的API
编辑 CatalogController.cs文件夹中的 catalog/Controllers文件夹中的文件,应用如下所示的修改。
// catalog/Controllers/CatalogController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
//using System.Net.Http.Headers; // 👈 removed code
using acme.Models;
namespace acme.Controllers;
public class CatalogController : Controller {
private readonly ILogger<HomeController> _logger;
private readonly HttpClient _httpClient; // 👈 new code
// 👇 changed code
public CatalogController(ILogger<HomeController> logger, HttpClient httpClient)
{
_logger = logger;
_httpClient = httpClient;
}
// 👆 changed code
// ...existing code...
[Authorize]
public async Task<IActionResult> Redeem(string prodid)
{
// 👇 changed code
var response = await _httpClient.GetFromJsonAsync<RedeemResponse>("redeem");
response.ProdId = prodid;
return View(response);
// 👆 changed code
}
}
你删除对 System.Net.Http.Headers.然后你定义了一个私有变量_httpClient ,它将被分配给通过控制器构造函数注入的HttpClient 实例。
最后,你只需改变方法的主体 Redeem()方法的主体来使用注入的HttpClient 实例。请注意,在这种情况下,你只需传递API端点的相对路径("redeem"),因为API的基本URL已经在HttpClient 命名的实例中配置好了。
再次运行你的目录应用程序,并尝试赎回其中的一个目录项目。你应该得到和以前一样的行为,但你的应用程序现在更强大了。
免费试用Auth0认证。开始吧→
总结
让我们回顾一下你在这篇文章中学到的调用受保护的API的方法。
你首先发现你需要的是获得一个访问令牌,并在调用API时将其作为一个承载令牌。然后,你下载并配置了一个样本程序,将这个目标付诸实践。
你学会了如何配置样本ASP.NET Core应用程序,以便在验证步骤中向Auth0请求一个访问令牌。然后你发现在你第一次尝试通过HttpClient 调用受保护的API时,检索和使用访问令牌是多么容易。
不幸的是,这种方法并不安全。它在代码冗余和资源管理方面有一些问题。出于这个原因,你实现了一个基于利用.NET依赖注入系统的不同方法。你定义了一个服务,根据IHttpClientFactory 接口创建HttpClient 实例。你还创建了一个消息处理程序,将访问令牌添加到每个HTTP请求中,并将该处理程序与HttpClient 实例绑定。
最后,你改变了实际的API调用,以使用这个新的集中式基础设施。