54 lines
2.3 KiB
C#
54 lines
2.3 KiB
C#
using Microsoft.Extensions.Caching.Distributed;
|
|
using Microsoft.Extensions.Options;
|
|
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
using TakeoutSaaS.Application.Identity.Abstractions;
|
|
using TakeoutSaaS.Application.Identity.Models;
|
|
using TakeoutSaaS.Infrastructure.Identity.Options;
|
|
|
|
namespace TakeoutSaaS.Infrastructure.Identity.Services;
|
|
|
|
/// <summary>
|
|
/// Redis 刷新令牌存储。
|
|
/// </summary>
|
|
public sealed class RedisRefreshTokenStore(IDistributedCache cache, IOptions<RefreshTokenStoreOptions> options) : IRefreshTokenStore
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
|
private readonly RefreshTokenStoreOptions _options = options.Value;
|
|
|
|
public async Task<RefreshTokenDescriptor> IssueAsync(long userId, DateTime expiresAt, CancellationToken cancellationToken = default)
|
|
{
|
|
var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(48));
|
|
var descriptor = new RefreshTokenDescriptor(token, userId, expiresAt, false);
|
|
|
|
var key = BuildKey(token);
|
|
var entryOptions = new DistributedCacheEntryOptions { AbsoluteExpiration = expiresAt };
|
|
await cache.SetStringAsync(key, JsonSerializer.Serialize(descriptor, JsonOptions), entryOptions, cancellationToken);
|
|
|
|
return descriptor;
|
|
}
|
|
|
|
public async Task<RefreshTokenDescriptor?> GetAsync(string refreshToken, CancellationToken cancellationToken = default)
|
|
{
|
|
var json = await cache.GetStringAsync(BuildKey(refreshToken), cancellationToken);
|
|
return string.IsNullOrWhiteSpace(json)
|
|
? null
|
|
: JsonSerializer.Deserialize<RefreshTokenDescriptor>(json, JsonOptions);
|
|
}
|
|
|
|
public async Task RevokeAsync(string refreshToken, CancellationToken cancellationToken = default)
|
|
{
|
|
var descriptor = await GetAsync(refreshToken, cancellationToken);
|
|
if (descriptor == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var updated = descriptor with { Revoked = true };
|
|
var entryOptions = new DistributedCacheEntryOptions { AbsoluteExpiration = updated.ExpiresAt };
|
|
await cache.SetStringAsync(BuildKey(refreshToken), JsonSerializer.Serialize(updated, JsonOptions), entryOptions, cancellationToken);
|
|
}
|
|
|
|
private string BuildKey(string token) => $"{_options.Prefix}{token}";
|
|
}
|