From 8526e5e0d270151ac3c4645c929361df06a6bb80 Mon Sep 17 00:00:00 2001 From: chfei Date: Fri, 28 Aug 2026 15:31:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20JWT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JwtServiceCollectionExtensions.cs | 8 +++--- StoreHub.Framework/Security/CurrentUser.cs | 12 +-------- StoreHub.Framework/Security/ICurrentUser.cs | 7 +---- .../Security/IOnlineUserStore.cs | 10 +++---- StoreHub.Framework/Security/ITokenService.cs | 4 +-- StoreHub.Framework/Security/JwtClaimTypes.cs | 6 ++--- StoreHub.Framework/Security/OnlineUserInfo.cs | 7 ++--- .../Security/OnlineUserStore.cs | 26 +++++++++---------- StoreHub.Framework/Security/TokenService.cs | 17 ++++++------ StoreHub/Controllers/AuthController.cs | 6 ++--- 10 files changed, 40 insertions(+), 63 deletions(-) diff --git a/StoreHub.Framework/DependencyInjection/JwtServiceCollectionExtensions.cs b/StoreHub.Framework/DependencyInjection/JwtServiceCollectionExtensions.cs index fb2fec9..803657a 100644 --- a/StoreHub.Framework/DependencyInjection/JwtServiceCollectionExtensions.cs +++ b/StoreHub.Framework/DependencyInjection/JwtServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -using Asp.Versioning.OpenApi; +using Asp.Versioning.OpenApi; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; @@ -68,12 +68,12 @@ public static class JwtServiceCollectionExtensions { IOnlineUserStore onlineUserStore = context.HttpContext.RequestServices.GetRequiredService(); ClaimsPrincipal? principal = context.Principal; - string? userIdValue = principal?.FindFirst(JwtClaimTypes.UserId)?.Value; + string? userName = principal?.FindFirst(JwtClaimTypes.UserName)?.Value; string? jti = principal?.FindFirst(JwtClaimTypes.Jti)?.Value; - if (!int.TryParse(userIdValue, out int userId) + if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(jti) - || !onlineUserStore.IsTokenActive(userId, jti)) + || !onlineUserStore.IsTokenActive(userName, jti)) { context.Fail(new SecurityTokenException("登录已失效,请重新登录")); } diff --git a/StoreHub.Framework/Security/CurrentUser.cs b/StoreHub.Framework/Security/CurrentUser.cs index 0d247fd..b4c279a 100644 --- a/StoreHub.Framework/Security/CurrentUser.cs +++ b/StoreHub.Framework/Security/CurrentUser.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using System.Globalization; using System.Security.Claims; @@ -24,16 +24,6 @@ public class CurrentUser : ICurrentUser /// public bool IsAuthenticated => Principal?.Identity?.IsAuthenticated == true; - /// - public int? Id - { - get - { - string? value = GetClaimValue(JwtClaimTypes.UserId); - return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int id) ? id : null; - } - } - /// public string? UserName => GetClaimValue(JwtClaimTypes.UserName); diff --git a/StoreHub.Framework/Security/ICurrentUser.cs b/StoreHub.Framework/Security/ICurrentUser.cs index 33989ce..26cef69 100644 --- a/StoreHub.Framework/Security/ICurrentUser.cs +++ b/StoreHub.Framework/Security/ICurrentUser.cs @@ -1,4 +1,4 @@ -using System.Security.Claims; +using System.Security.Claims; namespace StoreHub.Framework.Security; @@ -12,11 +12,6 @@ public interface ICurrentUser /// bool IsAuthenticated { get; } - /// - /// 当前用户ID(未登录为 null) - /// - int? Id { get; } - /// /// 当前登录账户名(未登录为 null) /// diff --git a/StoreHub.Framework/Security/IOnlineUserStore.cs b/StoreHub.Framework/Security/IOnlineUserStore.cs index 3f40302..5702077 100644 --- a/StoreHub.Framework/Security/IOnlineUserStore.cs +++ b/StoreHub.Framework/Security/IOnlineUserStore.cs @@ -1,4 +1,4 @@ -namespace StoreHub.Framework.Security; +namespace StoreHub.Framework.Security; /// /// 在线用户会话存储:基于 的进程内实现, @@ -12,14 +12,14 @@ public interface IOnlineUserStore void SignIn(OnlineUserInfo info); /// - /// 判断指定用户、指定令牌会话是否仍然有效(未被退出、未被新登录顶替)。 + /// 判断指定登录账户、指定令牌会话是否仍然有效(未被退出、未被新登录顶替)。 /// - bool IsTokenActive(int userId, string jti); + bool IsTokenActive(string userName, string jti); /// - /// 退出登录:销毁该用户的在线会话,令牌立即失效。 + /// 退出登录:销毁该账户的在线会话,令牌立即失效。 /// - void SignOut(int userId); + void SignOut(string userName); /// /// 获取当前在线人员列表。 diff --git a/StoreHub.Framework/Security/ITokenService.cs b/StoreHub.Framework/Security/ITokenService.cs index 7f43600..032bd78 100644 --- a/StoreHub.Framework/Security/ITokenService.cs +++ b/StoreHub.Framework/Security/ITokenService.cs @@ -18,9 +18,9 @@ public class TokenResult public string Token { get; set; } = string.Empty; /// - /// 过期时间(本地时间) + /// 有效时间(分钟) /// - public DateTime ExpiresAt { get; set; } + public uint ExpiresInMinutes { get; set; } } /// diff --git a/StoreHub.Framework/Security/JwtClaimTypes.cs b/StoreHub.Framework/Security/JwtClaimTypes.cs index d73afb7..8301fe3 100644 --- a/StoreHub.Framework/Security/JwtClaimTypes.cs +++ b/StoreHub.Framework/Security/JwtClaimTypes.cs @@ -1,13 +1,11 @@ -namespace StoreHub.Framework.Security; +namespace StoreHub.Framework.Security; /// /// JWT 自定义声明类型常量。 /// public static class JwtClaimTypes { - public const string UserId = "user_name"; - - /// 登录账户 + /// 登录账户(唯一) public const string UserName = "user_name"; /// 用户昵称 diff --git a/StoreHub.Framework/Security/OnlineUserInfo.cs b/StoreHub.Framework/Security/OnlineUserInfo.cs index 6491f82..e0d3b3a 100644 --- a/StoreHub.Framework/Security/OnlineUserInfo.cs +++ b/StoreHub.Framework/Security/OnlineUserInfo.cs @@ -1,14 +1,11 @@ -namespace StoreHub.Framework.Security; +namespace StoreHub.Framework.Security; /// /// 在线用户会话信息(单点登录:同一用户仅保留一个有效会话)。 /// public class OnlineUserInfo { - /// 用户ID - public int UserId { get; set; } - - /// 登录账户 + /// 登录账户(唯一,作为会话键) public string UserName { get; set; } = string.Empty; /// 用户昵称 diff --git a/StoreHub.Framework/Security/OnlineUserStore.cs b/StoreHub.Framework/Security/OnlineUserStore.cs index 844e083..da0b49a 100644 --- a/StoreHub.Framework/Security/OnlineUserStore.cs +++ b/StoreHub.Framework/Security/OnlineUserStore.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Memory; using System.Collections.Concurrent; namespace StoreHub.Framework.Security; @@ -11,7 +11,7 @@ public class OnlineUserStore : IOnlineUserStore private const string CacheKeyPrefix = "online:user:"; private readonly IMemoryCache _cache; // 在线人员索引:IMemoryCache 不提供公共枚举 API,这里用字典维护便于查询在线列表 - private readonly ConcurrentDictionary _onlineIndex = new(); + private readonly ConcurrentDictionary _onlineIndex = new(StringComparer.Ordinal); public OnlineUserStore(IMemoryCache cache) { @@ -23,33 +23,33 @@ public class OnlineUserStore : IOnlineUserStore { ArgumentNullException.ThrowIfNull(info); - // 以 userId 为键覆盖写入:旧会话(jti)被顶替,实现单点登录 - _cache.Set(GetKey(info.UserId), info, new MemoryCacheEntryOptions + // 以登录账户为键覆盖写入:旧会话(jti)被顶替,实现单点登录 + _cache.Set(GetKey(info.UserName), info, new MemoryCacheEntryOptions { // 缓存条目与令牌同时过期,过期后自动失效 AbsoluteExpiration = info.ExpiresAt, }); - _onlineIndex[info.UserId] = info; + _onlineIndex[info.UserName] = info; } /// - public bool IsTokenActive(int userId, string jti) + public bool IsTokenActive(string userName, string jti) { - if (string.IsNullOrWhiteSpace(jti)) + if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(jti)) { return false; } - return _cache.TryGetValue(GetKey(userId), out OnlineUserInfo? info) + return _cache.TryGetValue(GetKey(userName), out OnlineUserInfo? info) && info is not null && string.Equals(info.Jti, jti, StringComparison.Ordinal); } /// - public void SignOut(int userId) + public void SignOut(string userName) { - _cache.Remove(GetKey(userId)); - _onlineIndex.TryRemove(userId, out _); + _cache.Remove(GetKey(userName)); + _onlineIndex.TryRemove(userName, out _); } /// @@ -57,7 +57,7 @@ public class OnlineUserStore : IOnlineUserStore { DateTime now = DateTime.Now; List result = []; - foreach (KeyValuePair pair in _onlineIndex) + foreach (KeyValuePair pair in _onlineIndex) { if (pair.Value.ExpiresAt > now) { @@ -75,6 +75,6 @@ public class OnlineUserStore : IOnlineUserStore /// public int Count => GetOnlineUsers().Count; - private static string GetKey(int userId) => $"{CacheKeyPrefix}{userId}"; + private static string GetKey(string userName) => $"{CacheKeyPrefix}{userName}"; } diff --git a/StoreHub.Framework/Security/TokenService.cs b/StoreHub.Framework/Security/TokenService.cs index 244490b..280f2af 100644 --- a/StoreHub.Framework/Security/TokenService.cs +++ b/StoreHub.Framework/Security/TokenService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using System.Security.Claims; @@ -50,7 +50,7 @@ public class TokenService : ITokenService return new TokenResult { Token = token, - ExpiresAt = expires, + ExpiresInMinutes = (uint)_options.ExpiresMinutes, }; } @@ -74,9 +74,9 @@ public class TokenService : ITokenService ClaimsPrincipal principal = new(result.ClaimsIdentity); // 单点登录校验:会话必须仍然有效(未被退出、未被新登录顶替) - string? userIdValue = principal.FindFirst(JwtClaimTypes.UserId)?.Value; + string? userName = principal.FindFirst(JwtClaimTypes.UserName)?.Value; string? jti = principal.FindFirst(JwtClaimTypes.Jti)?.Value; - if (!int.TryParse(userIdValue, out int userId) || string.IsNullOrWhiteSpace(jti) || !_onlineUserStore.IsTokenActive(userId, jti)) + if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(jti) || !_onlineUserStore.IsTokenActive(userName, jti)) { return null; } @@ -94,16 +94,15 @@ public class TokenService : ITokenService /// private void RegisterSession(IEnumerable claims, string jti, DateTime loginTime, DateTime expiresAt) { - string? userIdValue = claims.FirstOrDefault(c => c.Type == JwtClaimTypes.UserId)?.Value; - if (!int.TryParse(userIdValue, out int userId)) + string? userName = claims.FirstOrDefault(c => c.Type == JwtClaimTypes.UserName)?.Value; + if (string.IsNullOrWhiteSpace(userName)) { - return; // 未携带用户ID的令牌不纳入在线会话管理 + return; // 未携带登录账户的令牌不纳入在线会话管理 } _onlineUserStore.SignIn(new OnlineUserInfo { - UserId = userId, - UserName = claims.FirstOrDefault(c => c.Type == JwtClaimTypes.UserName)?.Value ?? string.Empty, + UserName = userName, NickName = claims.FirstOrDefault(c => c.Type == JwtClaimTypes.NickName)?.Value ?? string.Empty, Jti = jti, LoginTime = loginTime, diff --git a/StoreHub/Controllers/AuthController.cs b/StoreHub/Controllers/AuthController.cs index d2e8b1e..fc2db5d 100644 --- a/StoreHub/Controllers/AuthController.cs +++ b/StoreHub/Controllers/AuthController.cs @@ -47,7 +47,6 @@ namespace StoreHub.Controllers var claims = new List { - new(JwtClaimTypes.UserId, user.Id.ToString()), new(JwtClaimTypes.UserName, user.UserName), new(JwtClaimTypes.NickName, user.NickName), }; @@ -63,9 +62,9 @@ namespace StoreHub.Controllers [Authorize] public ApiResult Logout() { - if (_currentUser.Id is int userId) + if (_currentUser.UserName is string username) { - _onlineUserStore.SignOut(userId); + _onlineUserStore.SignOut(username); } return ApiResult.Success(null); @@ -80,7 +79,6 @@ namespace StoreHub.Controllers { return ApiResult.Success(new { - UserId = _currentUser.Id, UserName = _currentUser.UserName, NickName = _currentUser.NickName, });