更新JWT
This commit is contained in:
1 parent
bb7162aaf6
commit
3769193696
16 files changed
+224
-141
No files matched your search
@@ -0,0 +1,77 @@
|
||||
using System.Security.Claims;
|
||||
using MAX.Framework.Security;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Store.Users.Tests;
|
||||
|
||||
public sealed class PersistentSessionTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task TokensSurviveRestart_AndLogoutOnlyRevokesCurrentSession()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), $"sessions-{Guid.NewGuid():N}.db");
|
||||
var options = Options.Create(new JwtOptions());
|
||||
TokenResult first;
|
||||
TokenResult second;
|
||||
string firstJti;
|
||||
try
|
||||
{
|
||||
using (var db = Open(path))
|
||||
{
|
||||
db.CodeFirst.SyncStructure<OnlineUserInfo>();
|
||||
var store = new OnlineUserStore(db);
|
||||
var tokens = new TokenService(options, store);
|
||||
first = tokens.CreateToken([new Claim(JwtClaimTypes.UserName, "same")], "192.0.2.10");
|
||||
second = tokens.CreateToken([new Claim(JwtClaimTypes.UserName, "same")], "2001:db8::1");
|
||||
firstJti = (await tokens.ValidateTokenAsync(first.Token))!.FindFirst(JwtClaimTypes.Jti)!.Value;
|
||||
Assert.NotNull(await tokens.ValidateTokenAsync(second.Token));
|
||||
Assert.Equal(2, store.Count);
|
||||
}
|
||||
// 新 ORM、新存储和 TokenService,模拟进程重启后的数据库恢复。
|
||||
using (var db = Open(path))
|
||||
{
|
||||
var store = new OnlineUserStore(db);
|
||||
var tokens = new TokenService(options, store);
|
||||
Assert.NotNull(await tokens.ValidateTokenAsync(first.Token));
|
||||
Assert.NotNull(await tokens.ValidateTokenAsync(second.Token));
|
||||
Assert.Contains(store.GetOnlineUsers(), x => x.LoginIp == "2001:db8::1");
|
||||
store.SignOutSession("other", firstJti);
|
||||
Assert.NotNull(await tokens.ValidateTokenAsync(first.Token));
|
||||
store.SignOutSession("same", firstJti);
|
||||
Assert.Null(await tokens.ValidateTokenAsync(first.Token));
|
||||
Assert.NotNull(await tokens.ValidateTokenAsync(second.Token));
|
||||
Assert.Single(store.GetOnlineUsers());
|
||||
Assert.NotNull(db.Select<OnlineUserInfo>().Where(x => x.Jti == firstJti).First().RevokedAt);
|
||||
}
|
||||
using (var db = Open(path))
|
||||
{
|
||||
var store = new OnlineUserStore(db);
|
||||
var tokens = new TokenService(options, store);
|
||||
Assert.Null(await tokens.ValidateTokenAsync(first.Token));
|
||||
Assert.NotNull(await tokens.ValidateTokenAsync(second.Token));
|
||||
store.SignOut("same");
|
||||
Assert.Null(await tokens.ValidateTokenAsync(second.Token));
|
||||
}
|
||||
}
|
||||
finally { File.Delete(path); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExpiredSessionsAreRejected_EvenWithinJwtClockTolerance()
|
||||
{
|
||||
using var db = Open(":memory:");
|
||||
db.CodeFirst.SyncStructure<OnlineUserInfo>();
|
||||
var store = new OnlineUserStore(db);
|
||||
var tokens = new TokenService(Options.Create(new JwtOptions()), store);
|
||||
var token = tokens.CreateToken([new Claim(JwtClaimTypes.UserName, "user")]);
|
||||
db.Update<OnlineUserInfo>().Set(x => x.ExpiresAt, DateTime.UtcNow.AddSeconds(-1))
|
||||
.Where(x => x.UserName == "user").ExecuteAffrows();
|
||||
Assert.Null(await tokens.ValidateTokenAsync(token.Token));
|
||||
Assert.Empty(store.GetOnlineUsers());
|
||||
Assert.Equal(1, db.Select<OnlineUserInfo>().Count());
|
||||
}
|
||||
|
||||
private static IFreeSql Open(string path) => new FreeSql.FreeSqlBuilder()
|
||||
.UseConnectionString(FreeSql.DataType.Sqlite, $"Data Source={path};Pooling=false")
|
||||
.Build();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><IsTestProject>true</IsTestProject><IsPackable>false</IsPackable></PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../Core/MAX.Framework/MAX.Framework.csproj" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5"><PrivateAssets>all</PrivateAssets></PackageReference>
|
||||
<PackageReference Include="FreeSql.Provider.Sqlite" Version="3.5.311" />
|
||||
<Using Include="Xunit" />
|
||||
<Compile Include="../Store.Users.Tests/DevelopmentTokenTests.cs" Link="DevelopmentTokenTests.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -28,6 +28,10 @@ public sealed class DevelopmentTokenTests
|
||||
["Jwt:DevelopmentToken:Token"] = "test-fixed-token",
|
||||
["Jwt:DevelopmentToken:UserName"] = "admin"
|
||||
});
|
||||
using var db = new FreeSql.FreeSqlBuilder()
|
||||
.UseConnectionString(FreeSql.DataType.Sqlite, "Data Source=:memory:").Build();
|
||||
db.CodeFirst.SyncStructure<OnlineUserInfo>();
|
||||
builder.Services.AddSingleton<IFreeSql>(db);
|
||||
builder.Services.AddStoreHubJwt(builder.Configuration);
|
||||
await using var app = builder.Build();
|
||||
app.UseAuthentication();
|
||||
|
||||
@@ -95,6 +95,7 @@ internal sealed class TestCurrentUser : ICurrentUser
|
||||
internal sealed class TestOnlineUsers : IOnlineUserStore
|
||||
{
|
||||
public HashSet<string> SignedOut { get; } = [];
|
||||
public void SignOutSession(string userName, string jti) => SignedOut.Add(userName);
|
||||
public void SignOut(string userName) => SignedOut.Add(userName);
|
||||
public void SignIn(OnlineUserInfo info) { }
|
||||
public bool IsTokenActive(string userName, string jti) => false;
|
||||
|
||||
@@ -159,6 +159,7 @@ sealed class Actor : ICurrentUser
|
||||
sealed class Sessions : IOnlineUserStore
|
||||
{
|
||||
public HashSet<string> Names { get; } = [];
|
||||
public void SignOutSession(string name, string jti) => Names.Add(name);
|
||||
public void SignOut(string name) => Names.Add(name);
|
||||
public void SignIn(OnlineUserInfo info) { }
|
||||
public bool IsTokenActive(string name, string jti) => false;
|
||||
|
||||
Reference in new issue
Block a user