如果http请求返回401错误,我将使用polly刷新授权令牌.我的完整解决方案基于:Refresh Token using Polly with Named Client,但相关部分是:

重试策略:

static IAsyncPolicy<HttpResponseMessage> GetAuthRefreshPolicy(IServiceProvider serviceProvider)
{
        return Policy<HttpResponseMessage>
            .HandleResult(response => response.StatusCode == HttpStatusCode.Unauthorized)
            .RetryAsync(async (handler, retry) => await serviceProvider.GetRequiredService<IAuthorisationService>().RefreshTokenAsync());
}

授权服务:

public class AuthorisationService : IAuthorisationService
{
    public String? AuthToken { get; private set; }

    private readonly IAuthenticationClient _AuthenticationClient;
    private readonly AuthOptions _AuthOptions;

    public AuthorisationService(IAuthenticationClient authenticationClient, IOptions<AuthOptions> options)
    {
        _AuthenticationClient = authenticationClient;
        _AuthOptions = options.Value;
    }

    public async Task RefreshTokenAsync()
    {
        this.AuthToken = await _AuthenticationClient.LoginAsync(_AuthOptions.User, _AuthOptions.Password);
    }
}

这在大多数情况下都工作得很好.我遇到的问题是,如果身份验证令牌当前无效,并且同时发出多个请求,因为它们是异步执行的,它们都将被无效令牌调用,失败并触发Polly重试策略,从而导致多个不必要的令牌刷新请求.

有没有办法设置Polly,以便如果请求失败并返回401,则所有后续请求都将等待,直到令牌成功刷新?或者,如果令牌已经刷新,则将重试try 排队并跳过请求新令牌?

我试着研究了Polly的隔板断路器等功能,但无法确定这些功能是否适合这个用例,或者是否有更简单的方法来锁定令牌刷新.

推荐答案

当我写答案时,我认为很明显,代码样本不是production-ready.

但让我们把注意力集中在你的具体问题上.为了避免多个并发的令牌刷新调用,您必须保护TokenService‘S RefreshToken方法.这可以通过许多不同的方式来实现.让我向您展示如何使用SemaphoreSlim来做到这一点:

public class TokenService : ITokenService
{
    private readonly SemaphoreSlim semaphore = new(1);
    private DateTime lastRefreshed = DateTime.UtcNow;
    public Token GetToken()
        => new() { Scheme = "Bearer", AccessToken = lastRefreshed.ToString("HH:mm:ss")}; 

    public async Task RefreshToken()
    {
        await semaphore.WaitAsync();

        try
        {
            //Use any arbitrary logic to detect simultaneous calls  
            if(lastRefreshed - DateTime.UtcNow < TimeSpan.FromSeconds(3)) 
            {
                Console.WriteLine("No refreshment happened");
                return;
            }
            
            Console.WriteLine("Refreshment happened");
            lastRefreshed = DateTime.UtcNow;
        }
        finally
        {
            semaphore.Release();
        }
    }
}

让我们测试一下代码

var service = new TokenService();
for (int i = 0; i < 10; i++)
{
    Task.Run(async() =>
    {
        await Task.Delay(Random.Shared.Next() % 1000);
        await service.RefreshToken();
    });
}

输出将是:

Refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened
No refreshment happened

Csharp相关问答推荐

在实际上是List T的 IESEARCH上多次调用First()是否不好?

使用LINQ to XML获取元素值列表是不起作用的

如何使用ConcurentDictionary属性上的属性将自定义System.Text.Json JsonConverter应用于该属性的值?

实体核心框架--HasColumnType和HasPrecision有什么不同?

如何在Windows 11任务调度程序中每1分钟重复一次任务?

如何在C#中使用Postman中的本地IP向本地主机上运行的本地API发出请求

从.Net 6 DLL注册和检索COM对象(Typelib导出:类型库未注册.(异常来自HRESULT:0x80131165))

TeamsBot SendActivityActivityTypes与ActivityTypes同步.键入不再起作用

当前代码Cosmos DB 3.37.1:PartitionKey key key mismatch exception

在C#中,将两个哈希集连接在一起的时间复杂度是多少?

.NET并发词典交换值

EFR32BG22 BLE在SPP模式下与PC(Windows 10)不连接

try 链接被委派者(多播委托)时,无法将获取运算符应用于类型为';方法组&39;和方法组';的操作数

在使用AWS SDK for.NET时,如何判断S3是否已验证我的对象的校验和?

获取混淆&Quot;模糊引用&Quot;错误

如何使用EPPlus C#在单个单元格中可视化显示多行文字

将C#类导入到PowerShell

从HTML元素获取 colored颜色

XmlSerializer在遇到XML属性(命名空间)时崩溃

使用c#中的Windows 10关机消息