65.9K
CodeProject 正在变化。 阅读更多。
Home

一种更优的处理C#/Xamarin应用中授权后端服务并透明处理刷新令牌续期的方案

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (1投票)

2020年9月30日

CPOL

4分钟阅读

viewsIcon

9277

如何消费已授权的后端服务,同时透明地处理刷新令牌续订

大多数情况下,我们的移动(客户端)应用程序会处理后端服务以使用微服务,例如ASP .NET Core Web API。

调用这些资源无需身份验证非常容易,因为您无需担心Bearer令牌(JWT)或刷新令牌,您只需要发出一个简单的httpclient请求,例如

 using (var client = new HttpClient())
  {
      var responseMessage = await client.GetAsync(URL);

      responseMessage.EnsureSuccessStatusCode();

      var jsonResponse = await responseMessage.Content.ReadAsStringAsync();

      var response = JsonConvert.DeserializeObject<IEnumerable<PersonInfo>>(jsonResponse);

      return response;
  }

在上面的代码中,我们只是向 WebAPI 发出一个HttpClient请求并读取返回的数据。请注意,我们没有通过传递任何 JWT(Bearer 令牌)进行身份验证,也没有处理令牌过期和 http 请求重试。

使用刷新令牌处理 Bearer 令牌续期

出于安全原因,Bearer 令牌又名 JWT(JSON Web 令牌)不会永远有效,当它们过期时,我们必须获取一个新的令牌才能与后端服务通信。

有两种方法可以做到这一点

  1. 强制用户重新进行身份验证/再次登录,或者
  2. 使用您现有的刷新令牌检索新的 Bearer 令牌 + 刷新令牌对。

由于第一种选择会让用户感到厌烦,因此我们今天将在本文中介绍第二种选择。

在调用期间处理 Bearer 令牌(JWT)/刷新令牌续期时,使用WebApi的典型工作流程如下所示

上述工作流程对于单个异步任务调用非常有效。

如果您同时发起多个异步任务调用,并且您的 Bearer 令牌在这些调用期间过期,那么所有调用都将尝试**独立地**使用它们拥有的刷新令牌来续期令牌,并且它们都将失败。

在这种情况下,您必须实现某种信号量,只允许第一个异步任务执行令牌续期,而其他异步任务则等待续期完成。

解决方案

我创建了一个名为RESTServiceXamarin.Forms服务类,负责处理 WebAPI 调用,这些类可以在我的Github上提供的示例代码中的 Xamarin Forms 项目中找到。

此类的主要目的是

  1. 对您的后端服务进行身份验证
  2. 执行您编写的任何HttpClient调用(GET/POST/PUT/DELETE)以使用您的 WebAPI
  3. 优雅地处理 JWT 过期
  4. 透明地强制续期访问令牌/JWT/Bearer 令牌
  5. 在续期令牌时同步多个异步任务的执行
  6. 在令牌续期后立即恢复您的 WebAPI 调用

Xamarin 示例代码

在我的 Github 上,您可以下载完整的 C# 示例/可运行解决方案,它由一个Xamarin.Forms项目和一个 ASP.NET Core 后端项目组成。

下面的屏幕截图来自示例项目。

后端项目假设

  • 我不会更深入地介绍后端项目的细节,因为它仅仅是为了演示。
  • 这个后端项目基本上是一个 ASPNET Core 3.1 项目,它使用内存中 Entity Framework Core,支持 JWT/刷新令牌,并包含两个 Web API,一个用于身份验证/刷新令牌,另一个用于演示正在检索的数据。
  • 为简洁起见,我只实现了用户名/密码身份验证。如果您使用社交身份验证,我认为提供的代码是一个很好的起点,您可以自己实现。
  • 为了测试目的,我将 Bearer 令牌 (JWT) 的过期时间配置为每 15 秒,刷新令牌的过期时间为 12 个月。您可以随意更改后端项目中的设置。
  • 我在后端项目的 *Startup.cs* 中硬编码了一个用户,因此您必须使用**用户名 = “test”** 和**密码 = “test”** 进行测试。

Using the Code

如上所述,RESTService 类可在示例代码中找到,位于Xamarin.Forms共享项目中的“_Services_”文件夹内。

身份验证

您必须先进行身份验证才能使用RESTService.ExecuteWithRetryAsync(),因此让我们首先通过AuthWithCredentialsAsync()方法进行身份验证

  var response = await RESTService.AuthWithCredentialsAsync(Username, Password);

  if (response.Success)
  {
      //Authenticated
  }
  else
  {
      //Authentication Failed
  }

成功进行身份验证后,接收到的 Bearer 令牌 (JWT) 和刷新令牌将存储在本地缓存中,也可以通过RESTService.BearerTokenRESTService.RefreshToken访问,以便以后使用。

调用您的 WebAPI

RESTService.ExecuteWithRetryAsync()接受一个 lambda 方法,该方法**应该是您自己的 httpclient 请求**,用于您想要检索/提交数据的WebAPI控制器。

return RESTService.ExecuteWithRetryAsync(async () =>
{
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Add("Authorization", $"bearer {RESTService.BearerToken}");

        var responseMessage = await client.GetAsync(URL);

        responseMessage.EnsureSuccessStatusCode();

        var jsonResponse = await responseMessage.Content.ReadAsStringAsync();

        var response = JsonConvert.DeserializeObject<IEnumerable<PersonInfo>>(jsonResponse);

        return response;
    }
});

几点说明

  • 当前的RESTService.BearerToken应在您自己的httpclient请求的标头(第 5 行)中提供。
  • 您应该始终使用EnsureSuccessStatusCode()方法(第 9 行),以便在 Bearer 令牌 (JWT) 过期的情况下,抛出HttpRequestException并内部处理它,以使用RefreshToken开始令牌续期。

就是这样。

希望您喜欢!

© . All rights reserved.