- 在JWT中添加permver权限版本号claim,用于实时权限变更检测 - 实现PermissionVersionMiddleware中间件,在认证后授权前校验权限版本 - 修改AssignPermissionsCommandHandler,角色权限变更时自增相关用户权限版本号 - 修改AssignRolesCommandHandler,用户角色变更时自增权限版本号强制重登录 - 集成Redis存储权限版本镜像,支持分布式环境下的版本同步 - 添加PermissionVersion字段到User实体,用于存储权限版本号 - 实现分布式登录失败计数与账号锁定功能,增强安全防护 - 配置文件中新增AccessTokenMinutes和RefreshTokenDays可配置项 - 更新CORS策略配置,添加127.0.0.1本地访问支持 - 完善异常处理,统一使用ErrorCode枚举进行错误码管理 - 添加SOP测试单元设计文档,规范端到端测试流程
83 lines
3.0 KiB
C#
83 lines
3.0 KiB
C#
using FluentAssertions;
|
||
using BuildFlow.Platform.Application.Auth.Commands;
|
||
using BuildFlow.Platform.Application.Auth.Handlers;
|
||
using BuildFlow.Shared.Domain.Exceptions;
|
||
|
||
namespace BuildFlow.Platform.Tests.Auth;
|
||
|
||
/// <summary>
|
||
/// 刷新令牌 Command 处理器单元测试。覆盖正常刷新 + Rotation、过期/吊销、不存在等路径。
|
||
/// </summary>
|
||
public class RefreshTokenCommandHandlerTests
|
||
{
|
||
private static async Task<(Platform.Infrastructure.Persistence.PlatformDbContext db, string refreshToken)> SeedTokenAsync(
|
||
bool revoked = false, DateTime? expiresAt = null, int permissionVersion = 0)
|
||
{
|
||
var db = PlatformTestDb.CreateDbContext();
|
||
var userId = Guid.NewGuid();
|
||
await PlatformTestDb.SeedUserAsync(db, userId, "refresher", permissionVersion: permissionVersion);
|
||
var token = "valid-refresh-token";
|
||
db.RefreshTokens.Add(new BuildFlow.Platform.Domain.Entities.RefreshToken
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
UserId = userId,
|
||
Token = token,
|
||
ExpiresAt = expiresAt ?? DateTime.UtcNow.AddDays(1),
|
||
IsRevoked = revoked,
|
||
CreatedBy = "test",
|
||
CreatedAt = DateTime.UtcNow,
|
||
UpdatedBy = "test",
|
||
UpdatedAt = DateTime.UtcNow,
|
||
});
|
||
await db.SaveChangesAsync();
|
||
return (db, token);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Handle_ValidToken_ShouldRotateAndIssueNewToken()
|
||
{
|
||
// Arrange
|
||
var (db, token) = await SeedTokenAsync();
|
||
var handler = new RefreshTokenCommandHandler(db, PlatformTestDb.CreateConfig());
|
||
|
||
// Act
|
||
var result = await handler.Handle(new RefreshTokenCommand(token), CancellationToken.None);
|
||
|
||
// Assert:新 token 签发 + 旧 token 被吊销(Rotation)
|
||
result.AccessToken.Should().NotBeNullOrWhiteSpace();
|
||
result.RefreshToken.Should().NotBe(token);
|
||
var oldToken = await db.RefreshTokens.FirstAsync(rt => rt.Token == token);
|
||
oldToken.IsRevoked.Should().BeTrue();
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Handle_RevokedToken_ShouldThrowExpired()
|
||
{
|
||
// Arrange
|
||
var (db, token) = await SeedTokenAsync(revoked: true);
|
||
var handler = new RefreshTokenCommandHandler(db, PlatformTestDb.CreateConfig());
|
||
|
||
// Act
|
||
var act = () => handler.Handle(new RefreshTokenCommand(token), CancellationToken.None);
|
||
|
||
// Assert
|
||
await act.Should().ThrowAsync<UnauthorizedException>()
|
||
.WithMessage("*刷新令牌已过期或已吊销*");
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Handle_NonExistentToken_ShouldThrowUnauthorized()
|
||
{
|
||
// Arrange
|
||
var db = PlatformTestDb.CreateDbContext();
|
||
var handler = new RefreshTokenCommandHandler(db, PlatformTestDb.CreateConfig());
|
||
|
||
// Act
|
||
var act = () => handler.Handle(new RefreshTokenCommand("nonexistent"), CancellationToken.None);
|
||
|
||
// Assert
|
||
await act.Should().ThrowAsync<UnauthorizedException>()
|
||
.WithMessage("*无效的刷新令牌*");
|
||
}
|
||
}
|