using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using Dpz.Core.Public.Entity.Auth;
using Dpz.Core.Service.OpenIddictStores;
using OpenIddict.Abstractions;
namespace Dpz.Core.Service;
public class TokenStoreService<T>(IRepository<T> repository, ILogger<TokenStoreService<T>> logger)
: IOpenIddictTokenStore<T>
where T : AuthToken, new()
{
public async ValueTask<long> CountAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Counting tokens");
return await repository.MongodbQueryable.LongCountAsync(
cancellationToken: cancellationToken
);
}
public async ValueTask<long> CountAsync<TResult>(
Func<IQueryable<T>, IQueryable<TResult>> query,
CancellationToken cancellationToken
)
{
logger.LogInformation("Counting tokens with custom query");
return await query(repository.MongodbQueryable)
.LongCountAsync(cancellationToken: cancellationToken);
}
public async ValueTask CreateAsync(T token, CancellationToken cancellationToken)
{
logger.LogInformation("Creating token {Id}", token.Id);
await repository.InsertAsync(token, cancellationToken);
}
public async ValueTask DeleteAsync(T token, CancellationToken cancellationToken)
{
logger.LogInformation("Deleting token {Id}", token.Id);
await repository.DeleteAsync(token.Id, cancellationToken);
}
public IAsyncEnumerable<T> FindAsync(
string? subject,
string? client,
string? status,
string? type,
CancellationToken cancellationToken
)
{
logger.LogInformation(
"Find tokens subject={Subject}, client={Client}, status={Status}, type={Type}",
subject,
client,
status,
type
);
var filter = Builders<T>.Filter.Empty;
if (!string.IsNullOrWhiteSpace(subject))
{
filter &= Builders<T>.Filter.Eq(x => x.Subject, subject);
}
if (!string.IsNullOrWhiteSpace(client) && ObjectId.TryParse(client, out var appId))
{
filter &= Builders<T>.Filter.Eq(x => x.ApplicationId, appId);
}
if (!string.IsNullOrWhiteSpace(status))
{
filter &= Builders<T>.Filter.Eq(x => x.Status, status);
}
if (!string.IsNullOrWhiteSpace(type))
{
filter &= Builders<T>.Filter.Eq(x => x.Type, type);
}
return repository.SearchForAsync(filter, cancellationToken);
}
public IAsyncEnumerable<T> FindByApplicationIdAsync(
string identifier,
CancellationToken cancellationToken
)
{
logger.LogInformation("Find tokens by application id {Identifier}", identifier);
if (!ObjectId.TryParse(identifier, out var appId))
{
return AsyncEnumerable.Empty<T>();
}
var filter = Builders<T>.Filter.Eq(x => x.ApplicationId, appId);
return repository.SearchForAsync(filter, cancellationToken);
}
public IAsyncEnumerable<T> FindByAuthorizationIdAsync(
string identifier,
CancellationToken cancellationToken
)
{
logger.LogInformation("Find tokens by authorization id {Identifier}", identifier);
if (!ObjectId.TryParse(identifier, out var authId))
{
return AsyncEnumerable.Empty<T>();
}
var filter = Builders<T>.Filter.Eq(x => x.AuthorizationId, authId);
return repository.SearchForAsync(filter, cancellationToken);
}
public async ValueTask<T?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
logger.LogInformation("Find token by id {Identifier}", identifier);
if (ObjectId.TryParse(identifier, out var id))
{
return await repository.FindAsync(id, cancellationToken);
}
return null;
}
public async ValueTask<T?> FindByReferenceIdAsync(
string identifier,
CancellationToken cancellationToken
)
{
logger.LogInformation("Find token by reference id {Identifier}", identifier);
return await repository
.SearchFor(x => x.ReferenceId == identifier)
.FirstOrDefaultAsync(cancellationToken);
}
public IAsyncEnumerable<T> FindBySubjectAsync(
string subject,
CancellationToken cancellationToken
)
{
logger.LogInformation("Find tokens by subject {Subject}", subject);
var filter = Builders<T>.Filter.Eq(x => x.Subject, subject);
return repository.SearchForAsync(filter, cancellationToken);
}
public async ValueTask<string?> GetApplicationIdAsync(
T token,
CancellationToken cancellationToken
)
{
return await ValueTask.FromResult(token.ApplicationId.ToString());
}
public async ValueTask<TResult?> GetAsync<TState, TResult>(
Func<IQueryable<T>, TState, IQueryable<TResult>> query,
TState state,
CancellationToken cancellationToken
)
{
return await Task.FromResult(query(repository.MongodbQueryable, state).FirstOrDefault());
}
public async ValueTask<string?> GetAuthorizationIdAsync(
T token,
CancellationToken cancellationToken
)
{
return await ValueTask.FromResult(token.AuthorizationId.ToString());
}
public async ValueTask<DateTimeOffset?> GetCreationDateAsync(
T token,
CancellationToken cancellationToken
)
{
var result = token.CreationDate is { } dt
? new DateTimeOffset(DateTime.SpecifyKind(dt, DateTimeKind.Utc))
: (DateTimeOffset?)null;
return await ValueTask.FromResult(result);
}
public async ValueTask<DateTimeOffset?> GetExpirationDateAsync(
T token,
CancellationToken cancellationToken
)
{
var result = token.ExpirationDate is { } dt
? new DateTimeOffset(DateTime.SpecifyKind(dt, DateTimeKind.Utc))
: (DateTimeOffset?)null;
return await ValueTask.FromResult(result);
}
public async ValueTask<string?> GetIdAsync(T token, CancellationToken cancellationToken)
{
return await ValueTask.FromResult(token.Id.ToString());
}
public async ValueTask<string?> GetPayloadAsync(T token, CancellationToken cancellationToken)
{
return await ValueTask.FromResult(token.Payload);
}
public ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(
T token,
CancellationToken cancellationToken
)
{
return ValueTask.FromResult(
OpenIddictBsonPropertySerializer.ToProperties(token.Properties)
);
}
public async ValueTask<DateTimeOffset?> GetRedemptionDateAsync(
T token,
CancellationToken cancellationToken
)
{
var result = token.RedemptionDate is { } dt
? new DateTimeOffset(DateTime.SpecifyKind(dt, DateTimeKind.Utc))
: (DateTimeOffset?)null;
return await ValueTask.FromResult(result);
}
public async ValueTask<string?> GetReferenceIdAsync(
T token,
CancellationToken cancellationToken
)
{
return await ValueTask.FromResult(token.ReferenceId);
}
public async ValueTask<string?> GetStatusAsync(T token, CancellationToken cancellationToken)
{
return await ValueTask.FromResult(token.Status);
}
public async ValueTask<string?> GetSubjectAsync(T token, CancellationToken cancellationToken)
{
return await ValueTask.FromResult(token.Subject);
}
public async ValueTask<string?> GetTypeAsync(T token, CancellationToken cancellationToken)
{
return await ValueTask.FromResult(token.Type);
}
public async ValueTask<T> InstantiateAsync(CancellationToken cancellationToken)
{
return await ValueTask.FromResult(new T { Id = ObjectId.GenerateNewId() });
}
public IAsyncEnumerable<T> ListAsync(
int? count,
int? offset,
CancellationToken cancellationToken
)
{
logger.LogInformation(
"List tokens with pagination: offset={Offset}, count={Count}",
offset,
count
);
var options = new FindOptions<T>();
if (offset.HasValue)
{
options.Skip = offset.Value;
}
if (count.HasValue)
{
options.Limit = count.Value;
}
return repository.SearchForAsync(Builders<T>.Filter.Empty, options, cancellationToken);
}
public async IAsyncEnumerable<TResult> ListAsync<TState, TResult>(
Func<IQueryable<T>, TState, IQueryable<TResult>> query,
TState state,
[EnumeratorCancellation] CancellationToken cancellationToken
)
{
logger.LogInformation("List tokens with custom query state={State}", state);
var projected = query(repository.MongodbQueryable, state);
await foreach (
var element in projected.ToAsyncEnumerable().WithCancellation(cancellationToken)
)
{
yield return element;
}
}
public async ValueTask<long> PruneAsync(
DateTimeOffset threshold,
CancellationToken cancellationToken
)
{
logger.LogInformation("Prune tokens threshold={Threshold}", threshold);
var builder = Builders<T>.Filter;
var filter = builder.Or(
builder.Lt(x => x.ExpirationDate, threshold.UtcDateTime),
builder.Lt(x => x.RedemptionDate, threshold.UtcDateTime)
);
var result = await repository.Collection.DeleteManyAsync(filter, cancellationToken);
return result.DeletedCount;
}
public async ValueTask<long> RevokeAsync(
string? subject,
string? client,
string? status,
string? type,
CancellationToken cancellationToken
)
{
logger.LogInformation(
"Revoke tokens subject={Subject}, client={Client}, status={Status}, type={Type}",
subject,
client,
status,
type
);
var builder = Builders<T>.Filter;
var filter = builder.Empty;
if (!string.IsNullOrWhiteSpace(subject))
{
filter &= builder.Eq(x => x.Subject, subject);
}
if (!string.IsNullOrWhiteSpace(client) && ObjectId.TryParse(client, out var appId))
{
filter &= builder.Eq(x => x.ApplicationId, appId);
}
if (!string.IsNullOrWhiteSpace(status))
{
filter &= builder.Eq(x => x.Status, status);
}
if (!string.IsNullOrWhiteSpace(type))
{
filter &= builder.Eq(x => x.Type, type);
}
var update = Builders<T>.Update.Set(x => x.Status, OpenIddictConstants.Statuses.Revoked);
var result = await repository.Collection.UpdateManyAsync(
filter,
update,
cancellationToken: cancellationToken
);
return result.ModifiedCount;
}
public async ValueTask<long> RevokeByApplicationIdAsync(
string identifier,
CancellationToken cancellationToken = new CancellationToken()
)
{
logger.LogInformation("Revoke tokens by application id {Identifier}", identifier);
if (!ObjectId.TryParse(identifier, out var appId))
{
return 0L;
}
var filter = Builders<T>.Filter.Eq(x => x.ApplicationId, appId);
var update = Builders<T>.Update.Set(x => x.Status, OpenIddictConstants.Statuses.Revoked);
var result = await repository.Collection.UpdateManyAsync(
filter,
update,
cancellationToken: cancellationToken
);
return result.ModifiedCount;
}
public async ValueTask<long> RevokeByAuthorizationIdAsync(
string identifier,
CancellationToken cancellationToken
)
{
logger.LogInformation("Revoke tokens by authorization id {Identifier}", identifier);
if (!ObjectId.TryParse(identifier, out var authId))
{
return 0L;
}
var filter = Builders<T>.Filter.Eq(x => x.AuthorizationId, authId);
var update = Builders<T>.Update.Set(x => x.Status, OpenIddictConstants.Statuses.Revoked);
var result = await repository.Collection.UpdateManyAsync(
filter,
update,
cancellationToken: cancellationToken
);
return result.ModifiedCount;
}
public async ValueTask<long> RevokeBySubjectAsync(
string subject,
CancellationToken cancellationToken = new CancellationToken()
)
{
logger.LogInformation("Revoke tokens by subject {Subject}", subject);
var filter = Builders<T>.Filter.Eq(x => x.Subject, subject);
var update = Builders<T>.Update.Set(x => x.Status, OpenIddictConstants.Statuses.Revoked);
var result = await repository.Collection.UpdateManyAsync(
filter,
update,
cancellationToken: cancellationToken
);
return result.ModifiedCount;
}
public async ValueTask SetApplicationIdAsync(
T token,
string? identifier,
CancellationToken cancellationToken
)
{
if (ObjectId.TryParse(identifier, out var appId))
{
token.ApplicationId = appId;
}
await ValueTask.CompletedTask;
}
public async ValueTask SetAuthorizationIdAsync(
T token,
string? identifier,
CancellationToken cancellationToken
)
{
if (ObjectId.TryParse(identifier, out var authId))
{
token.AuthorizationId = authId;
}
await ValueTask.CompletedTask;
}
public async ValueTask SetCreationDateAsync(
T token,
DateTimeOffset? date,
CancellationToken cancellationToken
)
{
token.CreationDate = date?.UtcDateTime;
await ValueTask.CompletedTask;
}
public async ValueTask SetExpirationDateAsync(
T token,
DateTimeOffset? date,
CancellationToken cancellationToken
)
{
token.ExpirationDate = date?.UtcDateTime;
await ValueTask.CompletedTask;
}
public async ValueTask SetPayloadAsync(
T token,
string? payload,
CancellationToken cancellationToken
)
{
token.Payload = payload;
await ValueTask.CompletedTask;
}
public ValueTask SetPropertiesAsync(
T token,
ImmutableDictionary<string, JsonElement> properties,
CancellationToken cancellationToken
)
{
token.Properties = OpenIddictBsonPropertySerializer.ToDocument(properties);
return ValueTask.CompletedTask;
}
public async ValueTask SetRedemptionDateAsync(
T token,
DateTimeOffset? date,
CancellationToken cancellationToken
)
{
token.RedemptionDate = date?.UtcDateTime;
await ValueTask.CompletedTask;
}
public async ValueTask SetReferenceIdAsync(
T token,
string? identifier,
CancellationToken cancellationToken
)
{
token.ReferenceId = identifier;
await ValueTask.CompletedTask;
}
public async ValueTask SetStatusAsync(
T token,
string? status,
CancellationToken cancellationToken
)
{
token.Status = status;
await ValueTask.CompletedTask;
}
public async ValueTask SetSubjectAsync(
T token,
string? subject,
CancellationToken cancellationToken
)
{
token.Subject = subject;
await ValueTask.CompletedTask;
}
public async ValueTask SetTypeAsync(T token, string? type, CancellationToken cancellationToken)
{
token.Type = type;
await ValueTask.CompletedTask;
}
public async ValueTask UpdateAsync(T token, CancellationToken cancellationToken)
{
logger.LogInformation("Update token {Id}", token.Id);
await repository.UpdateAsync(token, cancellationToken);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个用于 OpenIddict 框架的 Token(令牌)存储服务实现,使用 MongoDB 作为底层数据存储。
核心功能
类定义
public class TokenStoreService<T> : IOpenIddictTokenStore<T>
where T : AuthToken, new()
- 泛型类,
T必须继承自AuthToken并有无参构造函数 - 实现了 OpenIddict 的
IOpenIddictTokenStore<T>接口 - 使用主构造函数注入
IRepository<T>和ILogger
主要方法分类
1. 查询方法
- CountAsync: 统计令牌总数
- FindAsync: 多条件查询(subject/client/status/type)
- FindByApplicationIdAsync: 根据应用ID查找
- FindByAuthorizationIdAsync: 根据授权ID查找
- FindByIdAsync: 根据令牌ID查找
- FindByReferenceIdAsync: 根据引用ID查找
- FindBySubjectAsync: 根据主题查找
- ListAsync: 分页列表查询
2. CRUD 操作
- CreateAsync: 创建令牌
- UpdateAsync: 更新令牌
- DeleteAsync: 删除令牌
- InstantiateAsync: 实例化新令牌对象(自动生成 ObjectId)
3. 属性读取方法(Getter)
提供了一系列 Get*Async 方法获取令牌属性:
- ApplicationId、AuthorizationId
- CreationDate、ExpirationDate、RedemptionDate(日期转换为 UTC)
- Status、Type、Subject
- Payload、Properties(使用 BSON 序列化)
4. 属性设置方法(Setter)
对应的 Set*Async 方法用于设置令牌属性:
- 日期字段转换为 UTC DateTime
- ObjectId 字段进行解析验证
- Properties 使用自定义序列化器转换
5. 批量操作
PruneAsync: 清理过期/已赎回的令牌
// 删除 ExpirationDate 或 RedemptionDate 早于阈值的记录Revoke 系列方法: 撤销令牌(将 Status 设为 Revoked)
- RevokeAsync(多条件)
- RevokeByApplicationIdAsync
- RevokeByAuthorizationIdAsync
- RevokeBySubjectAsync
技术特点
MongoDB 集成
- 使用 MongoDB Filter/Update Builders 构建查询
- ObjectId 类型转换和验证
- 异步枚举支持
日志记录
- 所有关键操作都记录日志
- 包含操作参数便于追踪
异步模式
- 全部使用
ValueTask/IAsyncEnumerable - 支持
CancellationToken取消操作
- 全部使用
数据转换
- DateTime ↔ DateTimeOffset(UTC 时区处理)
- ObjectId ↔ string
- Properties ↔ BSON Document(通过
OpenIddictBsonPropertySerializer)
空值处理
- ObjectId 解析失败返回空集合或 0
- 可空日期字段的安全转换
使用场景
这个服务是 OpenIddict 在 MongoDB 环境下管理 OAuth 2.0 / OpenID Connect 令牌的核心组件,负责令牌的完整生命周期管理。
AI 正在分析代码…
评论加载中...