安全和用户体验是应用开发的两个基本方面。要把它们结合起来并不容易,但有一些技术可以让你在这两者之间取得良好的平衡。其中一个例子是使用刷新令牌:当你的应用程序调用API时,刷新令牌提供了安全性,而不影响用户体验。通过探索如何在调用API的ASP.NET Core应用程序中管理刷新令牌,了解刷新令牌如何帮助你实现这种平衡。
刷新令牌的必要性
访问令牌授权你的应用程序调用一个受保护的API。你在你的HTTP请求中使用它们作为承载令牌,如下面的例子所示:
GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM
作为一个不记名的令牌,意味着无论谁拥有该令牌,都可以用它来证明他们被授权访问一个资源,例如调用一个API。
为了防止访问令牌落入坏人之手,你需要应用一些策略,比如使用HTTPS以避免令牌被攻击者截获,避免将其存储在容易获取的存储器中,等等。
创建寿命较短的访问令牌是有助于减少被攻击风险的策略之一。由于访问令牌即将到期,攻击者能够窃取仍然有效的令牌的机会就会减少。Auth0允许你通过仪表板设置你的访问令牌的寿命。
这真是太棒了!但是当用户在使用你的应用程序时,访问令牌过期了怎么办?Auth0在登录时发放访问令牌。因此,你的用户需要再次认证以获得新的有效的访问令牌。显然,这不是一个好的用户体验。
刷新令牌的出现弥补了这种情况。它们是特殊的令牌,让应用程序获得新的访问令牌,而不必要求用户再次登录。以下是它们如何在高水平上工作:
- 在登录时,你的应用程序请求一个刷新令牌以及ID和访问令牌。
- 当你的应用程序需要调用一个API并发现访问令牌已经过期时,它通过发送刷新令牌向Auth0请求一个新的访问令牌。
- Auth0向你的应用程序发送一个新的访问令牌和一个新的刷新令牌。
- 你的应用程序使用新的访问令牌来调用API,当这个新的访问令牌过期时,将使用新的刷新令牌。提供一个新的刷新令牌有助于减轻重放攻击的风险。
现在你对刷新令牌有了高层次的理解,以及为什么你应该使用它们。但是,你如何在ASP.NET Core应用程序中实际使用它们?让我们开始我们的旅程。
设置示例应用程序
为了向你展示如何在ASP.NET Core应用程序中使用刷新令牌,你将使用一个示例项目,实现一个允许用户用积分兑换奖励的Web应用程序。该项目由两个元素组成:一个作为用户前端的ASP.NET Core MVC应用程序和一个执行实际积分兑换的API。
如果你想了解示例应用程序实现的细节,请查看这篇文章。它还会告诉你在ASP.NET Core应用程序中调用受保护的API的正确方法。
让我们通过在终端窗口运行以下命令,从GitHub下载示例应用程序。
git clone --branch starting-point --single-branch https://github.com/auth0-blog/refresh-token-aspnet-core.git
你会得到一个 refresh-token-aspnet-core文件夹,其中有两个子文件夹:catalog 和 redemption-api.catalog 文件夹包含一个ASP.NET Core MVC应用程序的代码,该程序向认证用户显示一个奖励目录。这个文件夹 redemption-api文件夹包含一个ASP.NET Core Web API的代码,允许用户兑换他们的积分以获得奖励。
要运行该示例应用程序,你需要在你的机器上安装.NET 6.0 SDK。
如果这是你第一次在你的机器上运行ASP.NET Core应用程序,请确保信任HTTPS开发证书。
虽然样本项目使用的是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",
"ClientSecret": "YOUR_CLIENT_SECRET",
"Audience": "YOUR_UNIQUE_IDENTIFIER"
},
"RedemptionApiBaseUrl": "https://localhost:7151"
}
替换占位符 YOUR_AUTH0_DOMAIN, YOUR_CLIENT_ID, , 和 YOUR_CLIENT_SECRET用基本信息部分的应用程序设置中的相应数值替换。替换 YOUR_UNIQUE_IDENTIFIER占位符替换为 https://redemption-api.com.稍后你会了解到这一点。
配置API
现在我们来配置赎回API。转到Auth0仪表板的API部分,点击创建API按钮。然后为你的API提供一个友好的名字(例如,Redemption API)和一个URI格式的唯一标识符(例如。 https://redemption-api.com).确保你在这里提供的标识符与你在目录应用程序配置文件中提供的Audience 值相同。保留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,如果你保留了上面建议的值)。这与你在目录应用程序中分配给Audience 键的值相同。
运行应用程序
现在,让我们运行应用程序,以检查一切是否按预期进行。
在 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仪表板的API部分,选择你之前配置的API。然后点击设置标签,向下滚动到表格中的令牌设置部分。将Token Expiration和Token Expiration For Browser Flows字段的值设置为60,如下图所示。

这个值将为这个API发出的访问令牌的有效期设置为60秒。
虽然你不会在样本程序中使用浏览器流量,但你仍然需要改变它,因为它的值不能低于令牌有效期的值。
设置完这些值后,向下滚动并点击保存按钮。
调整令牌过期容忍度
现在,打开 Program.cs文件夹中的 redemption-api文件夹中的文件,并应用下面所强调的修改。
using System; // 👈 new code
using Microsoft.AspNetCore.Authentication.JwtBearer;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = $"https://{builder.Configuration["Auth0:Domain"]}";
options.TokenValidationParameters =
new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = builder.Configuration["Auth0:Audience"],
ValidIssuer = $"{builder.Configuration["Auth0:Domain"]}",
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero // 👈 new code
};
});
builder.Services.AddAuthorization();
// ...existing code...
你添加了对System 命名空间的引用,并将零值分配给ClockSkew 属性。
这种改变在生产中可能没有必要,但在这个测试中你需要它,以避免等待很长时间才认为令牌真正过期。事实上,在默认情况下,.NET对令牌过期的容忍度为5分钟。这意味着你必须在访问令牌过期前至少等待6分钟:Auth0设置的一分钟加上5分钟的容忍度。如果你不想应用默认的容忍度,你必须将ClockSkew 属性设置为零秒。
对容差值的需求是由世界各地的服务器时钟可能不匹配决定的。要了解更多关于这个问题的一般信息,请阅读本文档。关于时钟偏移和令牌的更多细节,请看这个。
在这个改变之后,通过dotnet run 命令重新启动你的API。
测试令牌的过期情况
现在,确保你已经注销了目录应用程序并再次登录。这一次你应该得到一个访问令牌,它的有效期只有60秒。
进入目录,点击其中一个 "立即兑换!"按钮。如果你在登录后一分钟内这样做,你应该得到一个像下面这样的屏幕或一个告诉你积分不足的信息(API生成随机响应)。

等待一分钟或更长时间,然后重新尝试点击其中一个 "立即兑换!"按钮。这一次,你会被重定向到Auth0通用登录页面,再次进行认证并获得一个新的访问令牌。由于你在Auth0上的会话没有过期,你可能不会被要求再次插入你的凭证,但你将登陆到目录应用程序的主页。

这不是一个好的用户体验!
让我们看看如何通过使用刷新令牌进行补救。
启用刷新令牌支持
使用刷新令牌的第一步是使你的API支持它们。另一个加强其安全性的步骤是启用刷新令牌旋转。让我们看看如何做到这一点。
为你的API启用刷新令牌支持
再次,进入Auth0仪表板的API部分,选择你的API。然后点击设置标签,向下滚动到访问设置部分。在这里,启用允许离线访问开关,如下图所示。

不要忘记点击 "保存"按钮。
为你的应用程序启用刷新令牌旋转
现在进入Auth0仪表板的应用程序部分,选择你的网络应用。然后点击设置标签,向下滚动到刷新令牌旋转部分。拨动旋转开关,启用刷新令牌旋转,如下图所示。

向下滚动并点击保存更改按钮。
申请您的刷新令牌
一旦在Auth0端配置好了一切,让我们看看在代码端该怎么做。
为了使用刷新令牌,目录应用程序需要在用户登录时请求刷新令牌,以及一个ID和访问令牌。
样本ASP.NET Core MVC应用程序使用Auth0 ASP.NET Core Authentication SDK与Auth0互动。这隐藏了管理基于OpenID Connect的登录事务的基本复杂性,并使请求刷新令牌变得非常容易。事实上,你需要做的就是在登录时请求刷新令牌。 Program.cs``catalog 文件,并应用以下简单的修改。
// 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"];
options.ClientSecret = builder.Configuration["Auth0:ClientSecret"];
options.Scope = "openid profile email";
})
.WithAccessToken(options =>
{
options.Audience = builder.Configuration["Auth0:Audience"];
options.UseRefreshTokens = true; // 👈 new code
});
builder.Services.AddControllersWithViews();
// ...existing code...
你只需添加一行,将 true到访问令牌的UseRefreshTokens 选项。就这样!
重新启动目录应用程序,并确保你已经注销了它。然后再次登录。你会得到一个像下面这样的同意屏幕。

请注意,除了通知你应用程序要访问你的个人资料的数据外,这个屏幕还要求你授权允许离线访问。这基本上意味着你允许该应用程序使用刷新令牌。
一旦你接受了这个屏幕,就去目录页面,重复你之前做的步骤,测试访问令牌的有效期。这一次,你不应该再遇到之前的问题:你应该能够无缝地兑换奖励,即使在访问令牌过期之后。事实上,SDK在发现当前访问令牌过期时,会处理请求新访问令牌的过程。
免费试用Auth0认证。开始吧→
总结
本文在实践中告诉你如何通过使用刷新令牌来平衡安全需求和用户体验需求。
你首先了解到,在调用受访问令牌保护的API时,刷新令牌可以提供更大的安全性。在下载了示例程序并将其配置为支持Auth0的认证和授权后,你对访问令牌的过期值做了一些实验。你意识到,减少访问令牌的寿命以减少可能的破坏风险会给你的应用程序的用户体验带来问题。
所以你通过Auth0仪表板启用了刷新令牌支持,并对你现有的代码做了一个非常简单的修改。由于Auth0 ASP.NET Core Authentication SDK,刷新令牌管理发生在幕后,你的应用程序的用户体验和它的安全性是安全的。