using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; using System.Security.Claims; using System.Text.Encodings.Web; using ThingsGateway.Extension; namespace ThingsGateway.Admin.Application; /// /// 只适合 Demo 登录,会直接授权超管的权限 /// public class AdminOAuthHandler( IVerificatInfoService verificatInfoService, IAppService appService, ISysUserService sysUserService, ISysDictService configService, IOptionsMonitor options, ILoggerFactory logger, IUserAgentService userAgentService, UrlEncoder encoder ) : OAuthHandler(options, logger, encoder) where TOptions : AdminOAuthOptions, new() { static AdminOAuthHandler() { Task.Factory.StartNew(InsertableAsync, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); } /// /// 日志消息队列(线程安全) /// protected static readonly ConcurrentQueue _operateLogMessageQueue = new(); /// /// 创建访问日志 /// private static async Task InsertableAsync() { var db = DbContext.GetDB(); var appLifetime = App.RootServices!.GetService()!; while (!appLifetime.ApplicationStopping.IsCancellationRequested) { try { var data = _operateLogMessageQueue.ToListWithDequeue(); // 从日志队列中获取数据 if (data.Count > 0) { await db.InsertableWithAttr(data).ExecuteCommandAsync(appLifetime.ApplicationStopping).ConfigureAwait(false);//入库 } } catch (Exception ex) { NewLife.Log.XTrace.WriteException(ex); } finally { await Task.Delay(3000, appLifetime.ApplicationStopping).ConfigureAwait(false); } } } protected override async Task CreateTicketAsync( ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { Backchannel.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokens.AccessToken); properties.RedirectUri = Options.HomePath; properties.IsPersistent = true; var appConfig = await configService.GetAppConfigAsync().ConfigureAwait(false); int expire = appConfig.LoginPolicy.VerificatExpireTime; if (!string.IsNullOrEmpty(tokens.ExpiresIn) && int.TryParse(tokens.ExpiresIn, out var result)) { properties.ExpiresUtc = TimeProvider.System.GetUtcNow().AddSeconds(result); expire = (int)(result / 60.0); } var user = await Options.HandleUserInfoAsync(Context, tokens).ConfigureAwait(false); var loginEvent = await GetLogin(expire).ConfigureAwait(false); await UpdateUser(loginEvent).ConfigureAwait(false); identity.AddClaim(new Claim(ClaimConst.VerificatId, loginEvent.VerificatId.ToString())); identity.AddClaim(new Claim(ClaimConst.UserId, RoleConst.SuperAdminId.ToString())); identity.AddClaim(new Claim(ClaimConst.SuperAdmin, "true")); identity.AddClaim(new Claim(ClaimConst.OrgId, RoleConst.DefaultTenantId.ToString())); identity.AddClaim(new Claim(ClaimConst.TenantId, RoleConst.DefaultTenantId.ToString())); var context = new OAuthCreatingTicketContext( new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, user ); context.RunClaimActions(); await Events.CreatingTicket(context).ConfigureAwait(false); var httpContext = context.HttpContext; UserAgent? userAgent = null; var str = httpContext?.Request?.Headers?.UserAgent; if (!string.IsNullOrEmpty(str)) { userAgent = userAgentService.Parse(str); } var sysOperateLog = new SysOperateLog() { Name = this.Scheme.Name, Category = LogCateGoryEnum.Login, ExeStatus = true, OpIp = httpContext.GetRemoteIpAddressToIPv4(), OpBrowser = userAgent?.Browser, OpOs = userAgent?.Platform, OpTime = DateTime.Now, VerificatId = loginEvent.VerificatId, OpAccount = Options.GetName(user), ReqMethod = "OAuth", ReqUrl = string.Empty, ResultJson = string.Empty, ClassName = nameof(AdminOAuthHandler), MethodName = string.Empty, ParamJson = string.Empty, }; _operateLogMessageQueue.Enqueue(sysOperateLog); return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); } private async Task GetLogin(int expire) { var sysUser = await sysUserService.GetUserByIdAsync(RoleConst.SuperAdminId).ConfigureAwait(false);//获取用户信息 var loginEvent = new LoginEvent { Ip = appService.RemoteIpAddress, Device = appService.UserAgent?.Platform, Expire = expire, SysUser = sysUser, VerificatId = CommonUtils.GetSingleId() }; //获取verificat列表 var tokenTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire); //生成verificat信息 var verificatInfo = new VerificatInfo { Device = loginEvent.Device, Expire = loginEvent.Expire, VerificatTimeout = tokenTimeout, Id = loginEvent.VerificatId, UserId = loginEvent.SysUser.Id, LoginIp = loginEvent.Ip, LoginTime = loginEvent.DateTime }; //添加到verificat列表 verificatInfoService.Add(verificatInfo); return loginEvent; } /// /// 登录事件 /// /// /// 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(); //更新用户登录信息 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信息 } } /// 自定义 Token 异常 public class OAuthTokenException : Exception { public OAuthTokenException() : base() { } public OAuthTokenException(string? message, Exception? innerException) : base(message, innerException) { } public OAuthTokenException(string? message) : base(message) { } }