😎优化token处理逻辑:1、增加token版本号,修改密码或重置密码使老token失效 2、去掉用户黑名单等逻辑

This commit is contained in:
zuohuaijun 2025-04-04 03:26:02 +08:00
parent 93912d85f1
commit 171dbc5fe1
5 changed files with 78 additions and 89 deletions

View File

@ -11,6 +11,11 @@ namespace Admin.NET.Core;
/// </summary> /// </summary>
public class CacheConst public class CacheConst
{ {
/// <summary>
/// 用户Token版本缓存
/// </summary>
public const string KeyUserToken = "sys_user_token:";
/// <summary> /// <summary>
/// 用户接口缓存(接口集合) /// 用户接口缓存(接口集合)
/// </summary> /// </summary>
@ -81,16 +86,6 @@ public class CacheConst
/// </summary> /// </summary>
public const string KeyOpenAccessNonce = "sys_open_access_nonce:"; public const string KeyOpenAccessNonce = "sys_open_access_nonce:";
/// <summary>
/// 用户黑名单
/// </summary>
public const string KeyUserBlacklist = "sys_user_blacklist:";
/// <summary>
/// token黑名单
/// </summary>
public const string KeyTokenBlacklist = "sys_token_blacklist:";
/// <summary> /// <summary>
/// 系统配置缓存 /// 系统配置缓存
/// </summary> /// </summary>

View File

@ -25,8 +25,8 @@ public class SysConfigSeedData : ISqlSugarEntitySeedData<SysConfig>
new SysConfig{ Id=1300000000131, Name="日志保留天数", Code=ConfigConst.SysLogRetentionDays, Value="180", SysFlag=YesNoEnum.Y, Remark="日志保留天数(天)", OrderNo=40, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000131, Name="日志保留天数", Code=ConfigConst.SysLogRetentionDays, Value="180", SysFlag=YesNoEnum.Y, Remark="日志保留天数(天)", OrderNo=40, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000141, Name="记录操作日志", Code=ConfigConst.SysOpLog, Value="True", SysFlag=YesNoEnum.Y, Remark="是否记录操作日志", OrderNo=50, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000141, Name="记录操作日志", Code=ConfigConst.SysOpLog, Value="True", SysFlag=YesNoEnum.Y, Remark="是否记录操作日志", OrderNo=50, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000151, Name="单设备登录", Code=ConfigConst.SysSingleLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启单设备登录", OrderNo=60, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000151, Name="单设备登录", Code=ConfigConst.SysSingleLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启单设备登录", OrderNo=60, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000161, Name="Token过期时间", Code=ConfigConst.SysTokenExpire, Value="10080", SysFlag=YesNoEnum.Y, Remark="Token过期时间分钟", OrderNo=90, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000161, Name="Token过期时间", Code=ConfigConst.SysTokenExpire, Value="60", SysFlag=YesNoEnum.Y, Remark="Token过期时间分钟", OrderNo=90, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000171, Name="RefreshToken过期时间", Code=ConfigConst.SysRefreshTokenExpire, Value="20160", SysFlag=YesNoEnum.Y, Remark="刷新Token过期时间分钟一般 refresh_token 的有效时间 > 2 * access_token 的有效时间)", OrderNo=100, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000171, Name="RefreshToken过期时间", Code=ConfigConst.SysRefreshTokenExpire, Value="120", SysFlag=YesNoEnum.Y, Remark="刷新Token过期时间分钟一般 refresh_token 的有效时间 > 2 * access_token 的有效时间)", OrderNo=100, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000181, Name="发送异常日志邮件", Code=ConfigConst.SysErrorMail, Value="False", SysFlag=YesNoEnum.Y, Remark="是否发送异常日志邮件", OrderNo=110, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000181, Name="发送异常日志邮件", Code=ConfigConst.SysErrorMail, Value="False", SysFlag=YesNoEnum.Y, Remark="是否发送异常日志邮件", OrderNo=110, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000191, Name="域登录验证", Code=ConfigConst.SysDomainLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启域登录验证", OrderNo=120, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000191, Name="域登录验证", Code=ConfigConst.SysDomainLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启域登录验证", OrderNo=120, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
//new SysConfig{ Id=1300000000201, Name="行政区划同步层级", Code=ConfigConst.SysRegionSyncLevel, Value="3", SysFlag=YesNoEnum.Y, Remark="行政区划同步层级 1-省级,2-市级,3-区县级,4-街道级,5-村级", OrderNo=150, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, //new SysConfig{ Id=1300000000201, Name="行政区划同步层级", Code=ConfigConst.SysRegionSyncLevel, Value="3", SysFlag=YesNoEnum.Y, Remark="行政区划同步层级 1-省级,2-市级,3-区县级,4-街道级,5-村级", OrderNo=150, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },

View File

@ -122,7 +122,7 @@ public class SysAuthService : IDynamicApiController, ITransient
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009); _ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
// 判断账号是否被冻结 // 判断账号是否被冻结
if (user.Status == StatusEnum.Disable) throw Oops.Oh(ErrorCodeEnum.D1017); if (user.Status != StatusEnum.Enable) throw Oops.Oh(ErrorCodeEnum.D1017);
// 判断租户是否存在及状态 // 判断租户是否存在及状态
var tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetFirstAsync(u => u.Id == user.TenantId); var tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetFirstAsync(u => u.Id == user.TenantId);
@ -258,16 +258,19 @@ public class SysAuthService : IDynamicApiController, ITransient
user.LastLoginIp = _httpContextAccessor.HttpContext.GetRemoteIpAddressToIPv4(true); user.LastLoginIp = _httpContextAccessor.HttpContext.GetRemoteIpAddressToIPv4(true);
(user.LastLoginAddress, double? longitude, double? latitude) = CommonUtil.GetIpAddress(user.LastLoginIp); (user.LastLoginAddress, double? longitude, double? latitude) = CommonUtil.GetIpAddress(user.LastLoginIp);
user.LastLoginTime = DateTime.Now; user.LastLoginTime = DateTime.Now;
user.LastLoginDevice = CommonUtil.GetClientDeviceInfo(_httpContextAccessor.HttpContext?.Request?.Headers?.UserAgent); user.LastLoginDevice = CommonUtil.GetClientDeviceInfo(_httpContextAccessor.HttpContext?.Request?.Headers?.UserAgent);
await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new
{ {
u.LastLoginIp, u.LastLoginIp,
u.LastLoginAddress, u.LastLoginAddress,
u.LastLoginTime, u.LastLoginTime,
u.LastLoginDevice, u.LastLoginDevice,
}).ExecuteCommandAsync(); }).ExecuteCommandAsync();
// 发布系统登录事件 // 缓存用户Token版本
_sysCacheService.Set($"{CacheConst.KeyUserToken}{user.Id}", $"{user.TokenVersion}");
// 发布系统登录事件
await _eventPublisher.PublishAsync(UserEventTypeEnum.Login, user); await _eventPublisher.PublishAsync(UserEventTypeEnum.Login, user);
return new LoginOutput return new LoginOutput
@ -341,12 +344,16 @@ public class SysAuthService : IDynamicApiController, ITransient
public async void Logout() public async void Logout()
{ {
if (string.IsNullOrWhiteSpace(_userManager.Account)) if (string.IsNullOrWhiteSpace(_userManager.Account))
throw Oops.Oh(ErrorCodeEnum.D1011); throw Oops.Oh(ErrorCodeEnum.D1011);
// 增加无效Token黑名单 //// 更新用户Token版本号
var tokenExpire = await _sysConfigService.GetTokenExpire(); //await _sysUserRep.AsUpdateable()
var accessToken = _httpContextAccessor.HttpContext.Request.Headers.Authorization.ToString(); // .SetColumns(u => u.TokenVersion == u.TokenVersion + 1)
_sysCacheService.Set($"{CacheConst.KeyTokenBlacklist}{MD5Encryption.Encrypt(accessToken)}", $"{_userManager.UserId}-{_userManager.Account}-{_userManager.RealName}-{DateTime.Now}", TimeSpan.FromMinutes(tokenExpire)); // .Where(u => u.Id == _userManager.UserId)
// .ExecuteCommandAsync();
// 更新用户Token版本缓存
_sysCacheService.Set($"{CacheConst.KeyUserToken}{_userManager.UserId}", $"{_userManager.TokenVersion + 1}");
// 发布系统退出事件 // 发布系统退出事件
await _eventPublisher.PublishAsync(UserEventTypeEnum.Logout, _userManager); await _eventPublisher.PublishAsync(UserEventTypeEnum.Logout, _userManager);

View File

@ -140,19 +140,24 @@ public class SysUserService : IDynamicApiController, ITransient
if (await query.AnyAsync(u => u.Account == input.Account)) throw Oops.Oh(ErrorCodeEnum.D1003); if (await query.AnyAsync(u => u.Account == input.Account)) throw Oops.Oh(ErrorCodeEnum.D1003);
if (!string.IsNullOrWhiteSpace(input.Phone) && await query.AnyAsync(u => u.Phone == input.Phone)) throw Oops.Oh(ErrorCodeEnum.D1032); if (!string.IsNullOrWhiteSpace(input.Phone) && await query.AnyAsync(u => u.Phone == input.Phone)) throw Oops.Oh(ErrorCodeEnum.D1032);
await _sysUserRep.AsUpdateable(input.Adapt<SysUser>()).IgnoreColumns(true) input.TokenVersion++;
.IgnoreColumns(u => new { u.Password, u.Status, u.TenantId }).ExecuteCommandAsync(); var user = input.Adapt<SysUser>();
await _sysUserRep.AsUpdateable(user).IgnoreColumns(true).IgnoreColumns(u => new { u.Password, u.Status, u.TenantId }).ExecuteCommandAsync();
// 更新用户附属机构
await UpdateRoleAndExtOrg(input); await UpdateRoleAndExtOrg(input);
// 删除用户机构缓存 // 删除用户机构缓存
SqlSugarFilter.DeleteUserOrgCache(input.Id, _sysUserRep.Context.CurrentConnectionConfig.ConfigId.ToString()); SqlSugarFilter.DeleteUserOrgCache(input.Id, _sysUserRep.Context.CurrentConnectionConfig.ConfigId.ToString());
// 若账号的角色和组织架构发生变化,则强制下线账号进行权限更新 // 若账号的角色和组织架构发生变化,则强制下线账号进行权限更新
var user = await _sysUserRep.AsQueryable().ClearFilter().FirstAsync(u => u.Id == input.Id);
var roleIds = await _sysUserRoleService.GetUserRoleIdList(input.Id); var roleIds = await _sysUserRoleService.GetUserRoleIdList(input.Id);
if (input.OrgId != user.OrgId || !input.RoleIdList.OrderBy(u => u).SequenceEqual(roleIds.OrderBy(u => u))) if (input.OrgId != user.OrgId || !input.RoleIdList.OrderBy(u => u).SequenceEqual(roleIds.OrderBy(u => u)))
await _sysOnlineUserService.ForceOfflineByUserId(input.Id); {
// 强制下线账号和失效Token
await OfflineAndExpireToken(user);
}
// 更新域账号 // 更新域账号
await _sysUserLdapService.AddUserLdap(user.TenantId!.Value, user.Id, user.Account, input.DomainAccount); await _sysUserLdapService.AddUserLdap(user.TenantId!.Value, user.Id, user.Account, input.DomainAccount);
@ -194,12 +199,10 @@ public class SysUserService : IDynamicApiController, ITransient
var isOpenAccessUser = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysOpenAccess>>().IsAnyAsync(u => u.BindUserId == input.Id); var isOpenAccessUser = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysOpenAccess>>().IsAnyAsync(u => u.BindUserId == input.Id);
if (isOpenAccessUser) throw Oops.Oh(ErrorCodeEnum.D1030); if (isOpenAccessUser) throw Oops.Oh(ErrorCodeEnum.D1030);
// 设置账号Token黑名单 // 强制下线账号和失效Token
await SetUserBlackList(user, StatusEnum.Disable); await OfflineAndExpireToken(user);
// 强制账号下线
await _sysOnlineUserService.ForceOfflineByUserId(user.Id);
// 删除用户
await _sysUserRep.DeleteAsync(user); await _sysUserRep.DeleteAsync(user);
// 删除用户角色 // 删除用户角色
@ -258,8 +261,11 @@ public class SysUserService : IDynamicApiController, ITransient
if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) if (!Enum.IsDefined(typeof(StatusEnum), input.Status))
throw Oops.Oh(ErrorCodeEnum.D3005); throw Oops.Oh(ErrorCodeEnum.D3005);
// 设置账号Token黑名单 if (input.Status != StatusEnum.Enable)
await SetUserBlackList(user, input.Status); {
// 强制下线账号和失效Token
await OfflineAndExpireToken(user);
}
user.Status = input.Status; user.Status = input.Status;
var rows = await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Status }).ExecuteCommandAsync(); var rows = await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Status }).ExecuteCommandAsync();
@ -270,28 +276,6 @@ public class SysUserService : IDynamicApiController, ITransient
return rows; return rows;
} }
/// <summary>
/// 设置账号Token黑名单
/// </summary>
/// <param name="user"></param>
/// <param name="status"></param>
/// <returns></returns>
[NonAction]
private async Task SetUserBlackList(SysUser user, StatusEnum status)
{
// 禁用账号则增加黑名单,启用账号则移除黑名单
var sysCacheService = App.GetRequiredService<SysCacheService>();
if (status != StatusEnum.Enable)
{
sysCacheService.Set($"{CacheConst.KeyUserBlacklist}{user.Id}", $"{user.RealName}-{user.Phone}");
await _sysOnlineUserService.ForceOfflineByUserId(user.Id); // 强制账号下线
}
else
{
sysCacheService.Remove($"{CacheConst.KeyUserBlacklist}{user.Id}");
}
}
/// <summary> /// <summary>
/// 授权用户角色 🔖 /// 授权用户角色 🔖
/// </summary> /// </summary>
@ -301,14 +285,12 @@ public class SysUserService : IDynamicApiController, ITransient
[DisplayName("授权用户角色")] [DisplayName("授权用户角色")]
public async Task GrantRole(UserRoleInput input) public async Task GrantRole(UserRoleInput input)
{ {
//var user = await _sysUserRep.GetByIdAsync(input.UserId) ?? throw Oops.Oh(ErrorCodeEnum.D0009); var user = await _sysUserRep.GetByIdAsync(input.UserId) ?? throw Oops.Oh(ErrorCodeEnum.D0009);
//if (user.AccountType == AccountTypeEnum.SuperAdmin)
// throw Oops.Oh(ErrorCodeEnum.D1022);
await _sysUserRoleService.GrantUserRole(input); await _sysUserRoleService.GrantUserRole(input);
// 强制账号下线 // 强制下线账号和失效Token
await _sysOnlineUserService.ForceOfflineByUserId(input.UserId); await OfflineAndExpireToken(user);
// 发布更新用户角色事件 // 发布更新用户角色事件
await _eventPublisher.PublishAsync(UserEventTypeEnum.UpdateRole, input); await _eventPublisher.PublishAsync(UserEventTypeEnum.UpdateRole, input);
@ -367,14 +349,15 @@ public class SysUserService : IDynamicApiController, ITransient
// 更新密码和最新修改时间 // 更新密码和最新修改时间
user.LastChangePasswordTime = DateTime.Now; user.LastChangePasswordTime = DateTime.Now;
var rows = await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Password, u.LastChangePasswordTime }).ExecuteCommandAsync(); user.TokenVersion = user.TokenVersion + 1;
var rows = await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Password, u.LastChangePasswordTime, u.TokenVersion }).ExecuteCommandAsync();
// 强制下线账号和失效Token
await OfflineAndExpireToken(user);
// 发布修改用户密码事件 // 发布修改用户密码事件
await _eventPublisher.PublishAsync(UserEventTypeEnum.ChangePwd, input); await _eventPublisher.PublishAsync(UserEventTypeEnum.ChangePwd, input);
// 强制账号下线
await _sysOnlineUserService.ForceOfflineByUserId(user.Id);
return rows; return rows;
} }
@ -390,14 +373,14 @@ public class SysUserService : IDynamicApiController, ITransient
var password = await _sysConfigService.GetConfigValueByCode<string>(ConfigConst.SysPassword); var password = await _sysConfigService.GetConfigValueByCode<string>(ConfigConst.SysPassword);
user.Password = CryptogramUtil.Encrypt(password); user.Password = CryptogramUtil.Encrypt(password);
user.LastChangePasswordTime = null; user.LastChangePasswordTime = null;
await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Password, u.LastChangePasswordTime }).ExecuteCommandAsync(); user.TokenVersion = user.TokenVersion + 1;
await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Password, u.LastChangePasswordTime, u.TokenVersion }).ExecuteCommandAsync();
// 清空密码错误次数 // 清空密码错误次数缓存
var keyPasswordErrorTimes = $"{CacheConst.KeyPasswordErrorTimes}{user.Account}"; _sysCacheService.Remove($"{CacheConst.KeyPasswordErrorTimes}{user.Account}");
_sysCacheService.Remove(keyPasswordErrorTimes);
// 强制账号下线 // 强制下线账号和失效Token
await _sysOnlineUserService.ForceOfflineByUserId(user.Id); await OfflineAndExpireToken(user);
// 发布重置用户密码事件 // 发布重置用户密码事件
await _eventPublisher.PublishAsync(UserEventTypeEnum.ResetPwd, input); await _eventPublisher.PublishAsync(UserEventTypeEnum.ResetPwd, input);
@ -471,4 +454,17 @@ public class SysUserService : IDynamicApiController, ITransient
{ {
return await _sysUserExtOrgService.GetUserExtOrgList(userId); return await _sysUserExtOrgService.GetUserExtOrgList(userId);
} }
/// <summary>
/// 强制下线账号和失效Token
/// </summary>
/// <param name="user"></param>
private async Task OfflineAndExpireToken(SysUser user)
{
// 更新Token版本缓存
_sysCacheService.Set($"{CacheConst.KeyUserToken}{user.Id}", $"{user.TokenVersion + 1}");
// 强制下线账号
await _sysOnlineUserService.ForceOfflineByUserId(user.Id);
}
} }

View File

@ -38,26 +38,17 @@ namespace Admin.NET.Web.Core
{ {
// var serviceProvider = context.GetCurrentHttpContext().RequestServices; // var serviceProvider = context.GetCurrentHttpContext().RequestServices;
using var serviceScope = _serviceProvider.CreateScope(); using var serviceScope = _serviceProvider.CreateScope();
// 验证账号黑名单有则授权失败
var sysCacheService = serviceScope.ServiceProvider.GetRequiredService<SysCacheService>(); var sysCacheService = serviceScope.ServiceProvider.GetRequiredService<SysCacheService>();
if (sysCacheService.ExistKey($"{CacheConst.KeyUserBlacklist}{context.User.FindFirst(ClaimConst.UserId)?.Value}"))
{
context.Fail(new AuthorizationFailureReason(this, "账号已经黑名单,请联系相关管理人员。"));
context.GetCurrentHttpContext().SignoutToSwagger();
return;
}
// 验证Token黑名单有则授权失败
var accessToken = httpContext.Request.Headers.Authorization.ToString();
if (sysCacheService.ExistKey($"{CacheConst.KeyTokenBlacklist}{MD5Encryption.Encrypt(accessToken)}"))
{
context.Fail(new AuthorizationFailureReason(this, "Token已失效请重新登录。"));
context.GetCurrentHttpContext().SignoutToSwagger();
return;
}
// 验证Token版本号 // 验证Token版本号
var userId = context.User.FindFirst(ClaimConst.UserId)?.Value;
var tokenVersion = context.User.FindFirst(ClaimConst.TokenVersion)?.Value;
if (sysCacheService.Get<string>($"{CacheConst.KeyUserToken}{userId}") != tokenVersion)
{
context.Fail(new AuthorizationFailureReason(this, "令牌已失效,请重新登录。"));
context.GetCurrentHttpContext().SignoutToSwagger();
return;
}
// 验证租户有效期 // 验证租户有效期
var tenantId = context.User.FindFirst(ClaimConst.TenantId)?.Value; var tenantId = context.User.FindFirst(ClaimConst.TenantId)?.Value;
@ -66,7 +57,7 @@ namespace Admin.NET.Web.Core
var tenant = sysCacheService.Get<List<SysTenant>>(CacheConst.KeyTenant)?.FirstOrDefault(u => u.Id == long.Parse(tenantId)); var tenant = sysCacheService.Get<List<SysTenant>>(CacheConst.KeyTenant)?.FirstOrDefault(u => u.Id == long.Parse(tenantId));
if (tenant != null && tenant.ExpirationTime != null && DateTime.Now > tenant.ExpirationTime) if (tenant != null && tenant.ExpirationTime != null && DateTime.Now > tenant.ExpirationTime)
{ {
context.Fail(new AuthorizationFailureReason(this, "租户已过期,请联系相关管理员。")); context.Fail(new AuthorizationFailureReason(this, "租户已过期,请联系管理员。"));
context.GetCurrentHttpContext().SignoutToSwagger(); context.GetCurrentHttpContext().SignoutToSwagger();
return; return;
} }