- 新增 API 端 JWT 登录、refresh token 轮换和退出登录流程 - 新增 refresh token 实体、DbSet 配置和 EF Core 迁移 - 新增 PC 端授权码登录、本地全局 token 刷新、登出和鉴权服务 - 扩展统一端点模型,支持宿主过滤、角色鉴权、OpenAPI 元数据和 DI 服务处理器 - API 启用 JwtBearer 认证、Swagger UI 和认证端点注册 - PC 端注册认证服务,并按宿主过滤桌面拦截端点
85 lines
2.9 KiB
C#
85 lines
2.9 KiB
C#
using Avalonia_EFCore.Database;
|
|
using Avalonia_EFCore.Models;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Options;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace Avalonia_API.Authentication
|
|
{
|
|
public sealed class RefreshTokenService(AppDataContext db, IOptions<JwtOptions> options)
|
|
{
|
|
private readonly JwtOptions _options = options.Value;
|
|
|
|
public async Task<(string Token, ApiRefreshTokenEntity Entity)> CreateAsync(
|
|
int userId,
|
|
string? device,
|
|
string? ipAddress,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));
|
|
var entity = new ApiRefreshTokenEntity
|
|
{
|
|
UserId = userId,
|
|
TokenHash = HashToken(token),
|
|
ExpiresAt = DateTime.UtcNow.AddDays(_options.RefreshTokenDays),
|
|
Device = device,
|
|
IpAddress = ipAddress,
|
|
};
|
|
|
|
db.ApiRefreshTokens.Add(entity);
|
|
await db.SaveChangesAsync(cancellationToken);
|
|
return (token, entity);
|
|
}
|
|
|
|
public async Task<ApiRefreshTokenEntity?> FindActiveAsync(string? token, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(token))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var hash = HashToken(token);
|
|
var entity = await db.ApiRefreshTokens.FirstOrDefaultAsync(x => x.TokenHash == hash, cancellationToken);
|
|
return entity?.IsActive == true ? entity : null;
|
|
}
|
|
|
|
public async Task RevokeAsync(string? token, CancellationToken cancellationToken = default)
|
|
{
|
|
var entity = await FindActiveAsync(token, cancellationToken);
|
|
if (entity is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
entity.RevokedAt = DateTime.UtcNow;
|
|
await db.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<(string Token, ApiRefreshTokenEntity Entity)?> RotateAsync(
|
|
string? token,
|
|
string? device,
|
|
string? ipAddress,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var current = await FindActiveAsync(token, cancellationToken);
|
|
if (current is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var next = await CreateAsync(current.UserId, device, ipAddress, cancellationToken);
|
|
current.RevokedAt = DateTime.UtcNow;
|
|
current.ReplacedByTokenHash = next.Entity.TokenHash;
|
|
await db.SaveChangesAsync(cancellationToken);
|
|
return next;
|
|
}
|
|
|
|
private static string HashToken(string token)
|
|
{
|
|
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(token));
|
|
return Convert.ToHexString(bytes);
|
|
}
|
|
}
|
|
}
|