Files
ThingsGateway/src/Admin/ThingsGateway.Admin.Application/Services/Auth/AuthService.cs
2248356998 qq.com 556819c90c 10.11.83
2025-09-30 15:25:15 +08:00

403 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using ThingsGateway.DataEncryption;
using ThingsGateway.FriendlyException;
namespace ThingsGateway.Admin.Application;
public class AuthService : IAuthService
{
private readonly ISysDictService _configService;
private readonly ISysResourceService _sysResourceService;
private readonly IUserCenterService _userCenterService;
private readonly ISysUserService _sysUserService;
private readonly ISysOrgService _sysOrgService;
private IStringLocalizer<AuthService> _localizer;
private IVerificatInfoService _verificatInfoService;
private IAppService _appService;
public AuthService(ISysDictService configService, ISysResourceService sysResourceService,
ISysUserService userService, IUserCenterService userCenterService,
ISysOrgService sysOrgService,
IVerificatInfoService verificatInfoService, IAppService appService,
IStringLocalizer<AuthService> localizer)
{
_sysOrgService = sysOrgService;
_configService = configService;
_appService = appService;
_sysUserService = userService;
_sysResourceService = sysResourceService;
_userCenterService = userCenterService;
_localizer = localizer;
_verificatInfoService = verificatInfoService;
}
/// <summary>
/// 登录
/// </summary>
/// <param name="input">登录参数</param>
/// <param name="isCookie">cookie方式登录</param>
/// <returns>登录输出</returns>
public async Task<LoginOutput> LoginAsync(LoginInput input, bool isCookie = true)
{
var appConfig = await _configService.GetAppConfigAsync().ConfigureAwait(false);
//判断是否开启web访问
if (!appConfig.WebsitePolicy.WebStatus
&& input.Account != RoleConst.SuperAdmin)//如果禁用了网站并且不是超级管理员
{
throw Oops.Bah(appConfig.WebsitePolicy.CloseTip);
}
string? password = input.Password;
if (isCookie) //openApi登录不再需要解密
{
try
{
password = DESEncryption.Decrypt(input.Password);//解密
}
catch (Exception)
{
throw Oops.Bah(_localizer["MustDesc"]);
}
}
await BeforeLoginAsync(appConfig, input).ConfigureAwait(false);//登录前校验
var userInfo = await _sysUserService.GetUserByAccountAsync(input.Account, input.TenantId).ConfigureAwait(false);//获取用户信息
if (userInfo == null)
throw Oops.Bah(_localizer["UserNull", input.Account]);//用户不存在
if (userInfo.Password != password)
{
LoginError(appConfig.LoginPolicy, input.Account);//登录错误操作
}
var result = await ExecLogin(appConfig.LoginPolicy, input, userInfo, isCookie).ConfigureAwait(false);// 执行登录
return result;
}
/// <summary>
/// 注销当前用户
/// </summary>
public async Task LoginOutAsync()
{
if (UserManager.VerificatId == 0)
return;
var verificatId = UserManager.VerificatId;
//获取用户信息
var userinfo = await _sysUserService.GetUserByAccountAsync(UserManager.UserAccount, UserManager.TenantId).ConfigureAwait(false);
if (userinfo != null)
{
var loginEvent = new LoginEvent
{
Ip = _appService.RemoteIpAddress,
SysUser = userinfo,
VerificatId = verificatId
};
RemoveTokenFromCache(loginEvent);//移除verificat
}
await _appService.LoginOutAsync().ConfigureAwait(false);
}
#region
/// <summary>
/// 登录之前执行的方法
/// </summary>
/// <param name="appConfig">配置</param>
/// <param name="input">input</param>
private async Task BeforeLoginAsync(AppConfig appConfig, LoginInput input)
{
var tenantEnable = App.GetOptions<TenantOptions>()?.Enable ?? false;
if (tenantEnable)
{
//如果租户ID为空表示用域名登录
if (input.TenantId == null)
{
//获取域名
var origin = App.HttpContext.Request.Headers["Origin"].ToString();
// 如果Origin头不存在可以尝试使用Referer头作为备选
if (string.IsNullOrEmpty(origin))
origin = App.HttpContext.Request.Headers["Referer"].ToString();
//根据域名获取二级域名
var domain = origin.Split("//")[1].Split(".")[0];
//根据二级域名获取租户
var tenantList = await _sysOrgService.GetTenantListAsync().ConfigureAwait(false);
var tenant = tenantList.FirstOrDefault(x => x.Code.Equals(domain, StringComparison.OrdinalIgnoreCase));//获取租户默认是机构编码
if (tenant != null)
input.TenantId = tenant.Id;
else
input.TenantId = RoleConst.DefaultTenantId;
}
}
else
{
input.TenantId = RoleConst.DefaultTenantId;
}
var key = CacheConst.Cache_LoginErrorCount + input.Account + input.TenantId;//获取登录错误次数Key值
var errorCountCache = App.CacheService.Get<int>(key);//获取登录错误次数
if (errorCountCache >= appConfig.LoginPolicy.ErrorCount)
{
App.CacheService.SetExpire(key, TimeSpan.FromMinutes(appConfig.LoginPolicy.ErrorLockTime));//设置缓存
throw Oops.Bah(_localizer["PasswordError", appConfig.LoginPolicy.ErrorLockTime]);
}
}
/// <summary>
/// 执行登录
/// </summary>
/// <param name="loginPolicy">登录策略</param>
/// <param name="input">用户登录参数</param>
/// <param name="sysUser">用户信息</param>
/// <param name="isCookie">cookie方式登录</param>
/// <returns>登录输出结果</returns>
private async Task<LoginOutput> ExecLogin(LoginPolicy loginPolicy, LoginInput input, SysUser sysUser, bool isCookie = true)
{
if (sysUser.Status == false)
throw Oops.Bah(_localizer["UserDisable", sysUser.Account]);//账号已停用
var verificatId = CommonUtils.GetSingleId();
var expire = loginPolicy.VerificatExpireTime;
string accessToken = string.Empty;
string refreshToken = string.Empty;
if (!isCookie)
{
#region Token
//生成Token
accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
{
{
ClaimConst.UserId, sysUser.Id
},
{
ClaimConst.Account, sysUser.Account
},
{
ClaimConst.SuperAdmin, sysUser.RoleIdList.Contains(RoleConst.SuperAdminRoleId)
},
{
ClaimConst.VerificatId, verificatId
},
{
ClaimConst.OrgId, sysUser.OrgId
},
{
ClaimConst.TenantId, input.TenantId
}
});
// 生成刷新Token令牌
refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, expire * 2);
// 设置Swagger自动登录
App.HttpContext?.SigninToSwagger(accessToken);
// 设置响应报文头
App.HttpContext?.SetTokensOfResponseHeaders(accessToken, refreshToken);
#endregion Token
}
else
{
if (sysUser.ModuleList.Count == 0)
throw Oops.Bah(_localizer["UserNoModule"]);//未分配模块
var org = await _sysOrgService.GetSysOrgByIdAsync(sysUser.OrgId).ConfigureAwait(false);//获取机构
if (!org.Status) throw Oops.Bah(_localizer["OrgDisable"]);//机构冻结
#region cookie
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimConst.VerificatId, verificatId.ToString()));
identity.AddClaim(new Claim(ClaimConst.UserId, sysUser.Id.ToString()));
identity.AddClaim(new Claim(ClaimConst.Account, sysUser.Account));
identity.AddClaim(new Claim(ClaimConst.SuperAdmin, sysUser.RoleIdList.Contains(RoleConst.SuperAdminRoleId).ToString()));
identity.AddClaim(new Claim(ClaimConst.OrgId, sysUser.OrgId.ToString()));
identity.AddClaim(new Claim(ClaimConst.TenantId, input.TenantId?.ToString() ?? "0"));
await _appService.LoginAsync(identity, expire).ConfigureAwait(false);
#endregion cookie
}
//登录事件参数
var logingEvent = new LoginEvent
{
Ip = _appService.RemoteIpAddress,
Device = _appService.UserAgent?.Platform ?? "Unknown",
Expire = expire,
SysUser = sysUser,
VerificatId = verificatId
};
await WriteTokenToCache(loginPolicy, logingEvent).ConfigureAwait(false);//写入verificat到cache
await UpdateUser(logingEvent).ConfigureAwait(false);
//返回结果
return new LoginOutput
{
VerificatId = verificatId,
Account = sysUser.Account,
Id = sysUser.Id,
AccessToken = accessToken,
RefreshToken = refreshToken
};
}
/// <summary>
/// 登录错误反馈
/// </summary>
/// <param name="loginPolicy">登录策略</param>
/// <param name="userName">用户名称</param>
private void LoginError(LoginPolicy loginPolicy, string userName)
{
var key = CacheConst.Cache_LoginErrorCount + userName;//获取登录错误次数Key值
App.CacheService.Increment(key, 1);// 登录错误次数+1
App.CacheService.SetExpire(key, TimeSpan.FromMinutes(loginPolicy.ErrorResetTime));//设置过期时间
var errorCountCache = App.CacheService.Get<int>(key);//获取登录错误次数
throw Oops.Bah(_localizer["AuthErrorMax", loginPolicy.ErrorCount, loginPolicy.ErrorLockTime, errorCountCache]);//账号密码错误
}
/// <summary>
/// 从cache删除用户verificat
/// </summary>
/// <param name="loginEvent">登录事件参数</param>
private void RemoveTokenFromCache(LoginEvent loginEvent)
{
//更新verificat列表
_verificatInfoService.Delete(loginEvent.VerificatId);
}
/// <summary>
/// 单用户登录通知用户下线
/// </summary>
/// <param name="userId">用户Id</param>
private async Task SingleLogin(long userId)
{
var clientIds = _verificatInfoService.GetClientIdListByUserId(userId);
await NoticeUtil.UserLoginOut(new UserLoginOutEvent
{
Message = _localizer["SingleLoginWarn"],
ClientIds = clientIds,
}).ConfigureAwait(false);
}
/// <summary>
/// 登录事件
/// </summary>
/// <param name="loginEvent"></param>
/// <returns></returns>
private async Task UpdateUser(LoginEvent loginEvent)
{
var sysUser = loginEvent.SysUser;
#region /
var key = CacheConst.Cache_LoginErrorCount + sysUser.Account;//获取登录错误次数Key值
App.CacheService.Remove(key);//移除登录错误次数
//获取用户verificat列表
var userToken = _verificatInfoService.GetOne(loginEvent.VerificatId);
#endregion /
#region ,
sysUser.LastLoginIp = sysUser.LatestLoginIp;
sysUser.LastLoginTime = sysUser.LatestLoginTime;
sysUser.LatestLoginIp = loginEvent.Ip;
sysUser.LatestLoginTime = loginEvent.DateTime;
#endregion ,
using var db = DbContext.GetDB<SysUser>();
//更新用户登录信息
if (await db.UpdateableT(sysUser).UpdateColumns(it => new
{
it.LastLoginIp,
it.LastLoginTime,
it.LatestLoginIp,
it.LatestLoginTime,
}).ExecuteCommandAsync().ConfigureAwait(false) > 0)
App.CacheService.HashAdd(CacheConst.Cache_SysUser, sysUser.Id.ToString(), sysUser);//更新Cache信息
}
/// <summary>
/// 写入用户verificat到cache
/// </summary>
/// <param name="loginPolicy">登录策略</param>
/// <param name="loginEvent">登录事件参数</param>
private async Task WriteTokenToCache(LoginPolicy loginPolicy, LoginEvent loginEvent)
{
//获取verificat列表
var tokenTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire);
//生成verificat信息
var verificatInfo = new VerificatInfo
{
Device = loginEvent.Device ?? "Unknown",
Expire = loginEvent.Expire,
VerificatTimeout = tokenTimeout,
Id = loginEvent.VerificatId,
UserId = loginEvent.SysUser.Id,
LoginIp = loginEvent.Ip,
LoginTime = loginEvent.DateTime
};
//判断是否单用户登录
if (loginPolicy.SingleOpen)
{
await SingleLogin(loginEvent.SysUser.Id).ConfigureAwait(false);//单用户登录方法
}
//添加到verificat列表
_verificatInfoService.Add(verificatInfo);
}
#endregion
}
/// <summary>
/// 登录事件参数
/// </summary>
public class LoginEvent
{
/// <summary>
/// 时间
/// </summary>
public DateTime DateTime = DateTime.Now;
/// <summary>
/// 过期时间
/// </summary>
public int Expire { get; set; }
/// <summary>
/// Ip地址
/// </summary>
public string? Ip { get; set; }
/// <summary>
/// 用户信息
/// </summary>
public SysUser SysUser { get; set; }
/// <summary>
/// VerificatId
/// </summary>
public long VerificatId { get; set; }
/// <summary>
/// 登录设备
/// </summary>
public string Device { get; set; }
}