using DocumentFormat.OpenXml.Office2010.Excel; using Lazy.Captcha.Core; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using NPOI.SS.Formula.Functions; using System.Security.Claims; using YLErp.Configuration; namespace YLErp.Web.Controllers { public class AccountController : Controller { private const string _auth_login = "_auth_login_"; private readonly IHttpClientFactory _httpClientFactory; private readonly ICaptcha _captcha; public AccountController(IHttpClientFactory httpClientFactory, ICaptcha captcha) { _httpClientFactory = httpClientFactory; _captcha = captcha; } //--------------------------------------- // 安全逻辑: // 每个浏览器客户端应该有一个guid确定其唯一性 // 如果没有guid则在cookie中设置并需要输入验证码 // 如果有guid,则根据服务端缓存判断此浏览器是否上次登录失败 // 如果上次登录失败,则显示验证码输入,否则不需要 //--------------------------------------- public ActionResult Login(bool skipSso = false) { var keys = Request.Cookies.Keys.ToArray(); foreach (var key in keys) { if (key != _auth_login) { Response.Cookies.Delete(key); } } if (!skipSso && SsoLoginConfig.SSO_Enable && PS.Config.Company == CompanyEnum.天风) { return View("TianFengLogin"); } return LoginError(null); } public ActionResult Login2() { return Login(true); } /// /// 登录错误 /// private ActionResult LoginError(string errorMessage, Exception ex = null) { var strictAuthorize = PS.Config.ErpElement.StrictAuthorize; var model = new LogOnModel { ErrorMessage = errorMessage, NeedCaptcha = strictAuthorize, Exception = ex, LoadTime = DateTime.Now }; if (strictAuthorize) { var hasError = !string.IsNullOrEmpty(errorMessage); Request.Cookies.TryGetValue(_auth_login, out var guid); if (string.IsNullOrEmpty(guid) || guid.Length < 30) { guid = Guid.NewGuid().ToString("N"); Response.Cookies.Append(_auth_login, guid, new CookieOptions { HttpOnly = true, Expires = DateTimeOffset.Now.AddHours(1) }); } Server.CacheProvider.Set(_auth_login + guid, hasError ? "##" : "#", TimeSpan.FromMinutes(20)); var sid = guid + "#" + model.LoadTime.ToString("yyyyMMddHHmmss") + (model.NeedCaptcha ? "#1" : "#0"); model.SID = DataProtectHelper.Encrypt(sid); } return View(nameof(Login), model); } [HttpPost, ResponseCache(NoStore = true)] public async Task LoginHandle(LogOnModel model) { if (string.IsNullOrWhiteSpace(model?.UserName) || string.IsNullOrWhiteSpace(model?.Password)) { return LoginError("用户名和密码必须填写"); } if (model.LoadTime.Year < 2000) { return LoginError("登录失败,非正常请求"); } if (PS.Config.ErpElement.StrictAuthorize) { if (string.IsNullOrEmpty(model.SID) || DateTime.Now > model.LoadTime.AddMinutes(20)) { return LoginError("请求已过期,请重新登录"); } Request.Cookies.TryGetValue(_auth_login, out var guid); if (string.IsNullOrEmpty(guid)) { return LoginError("请求无效,请重新登录"); } try { var sids = DataProtectHelper.Decrypt(model.SID).Split('#'); if (sids.Length < 3 || sids[0] != guid || sids[1] != model.LoadTime.ToString("yyyyMMddHHmmss")) { return LoginError("请求参数失效,请重新登录"); } if (sids[2] == "1") { var key = _auth_login + guid; if (!_captcha.Validate(key, model.CaptchaCode)) { return LoginError("验证码错误"); } } } catch (Exception ex) { return LoginError("验证出错", ex); } } try { model.UserName = Decode(model.UserName); model.Password = Decode(model.Password); } catch (Exception ex) { return LoginError("验证出错", ex); } var errorNumStr = Server.CacheProvider.Get("loginUserError^" + model.UserName.ToLower())?.ToString(); var errorNum = string.IsNullOrEmpty(errorNumStr) ? 0 : Convert.ToInt32(errorNumStr); if (errorNum == 5&& PS.Config.ErpElement.StrictAuthorize) { return LoginError("账户已被锁定,请稍后再试"); } SystemUser user = null; if (SsoLoginConfig.SSO_Enable) { switch (model.UserName.ToLowerInvariant()) { case "admin": case "supera": break; default: if (PS.Config.Company == CompanyEnum.天风) { return LoginError("非管理员账户,请刷新页面后使用普通用户登录方式"); } break; } } if (user == null) { var result = SystemUser.CheckUser(model.UserName, model.Password, out user); if (result != UserCheckResult.验证通过) { var errorResult = result.ToString(); if (PS.Config.ErpElement.StrictAuthorize) { errorNum = errorNum + 1; if (errorNum < 5) { errorResult = $"{errorResult},您还有{5 - errorNum}次机会登录尝试"; } else { errorResult = $"{errorResult},账号已被锁定,请稍后再尝试!"; } Server.CacheProvider.Set("loginUserError^" + model.UserName.ToLower(), errorNum++, TimeSpan.FromMinutes(120)); } return LoginError(errorResult); } if (user == null || user.Id < 1) { return LoginError("验证失败," + result.ToString()); } } var userToken = Guid.NewGuid().ToString("N"); using (var db = DbContextFactory.GetErpBaseContext()) { user = db.SystemUsers.Find(user.Id); user.UserToken = userToken; db.SystemUserLoginfo.Add(new SystemUserLoginfo { IPAddress = HttpContext.Connection.RemoteIpAddress?.ToString(), LogTime = DateTime.Now, LogType = "登入", UserId = user.Id, UserName = user.Name, LoginName = model.UserName, MACAddress = "", LoadTime = model.LoadTime }); db.SaveChanges(); } //保存到cookie Server.CacheProvider.Set("loginUserError^" + model.UserName.ToLower(), 0, TimeSpan.FromMinutes(120)); var identity = UserManager.CreateIdentity(user.Id.ToString(), user.Name, userToken); await HttpContext.SignInAsync(AuthHelper.CookieAuthType, new ClaimsPrincipal(identity)); var jwtToken = AuthHelper.CreateJwtToken2(identity); Response.Cookies.Append("Access-Token", jwtToken, new CookieOptions { MaxAge = TimeSpan.FromSeconds(AuthHelper.ExpireInSeconds) }); Response.Cookies.Append("Access-Token-Encrypt", DataProtectHelper.Encrypt(jwtToken)); //保存到缓存 var userInfo = UserInfo.FromDbUser(user); Server.CacheProvider.Set("loginUser^" + user.Id, userInfo, TimeSpan.FromMinutes(30)); if (!PasswordValidator.Validate(model.Password)) { Server.CacheProvider.Set("简单密码" + user.Id, "是", TimeSpan.FromMinutes(60)); } return RedirectToAction("Index", "Home", new { g = Guid.NewGuid() }); } public ActionResult LogOut() { var userId = User.GetUserId(); var loginName = User.GetUserName(); if (userId > 0) { Server.CacheProvider.Remove("loginUser^" + userId); try { using (var db = Modules.DbContextFactory.GetErpBaseContext()) { var user = db.SystemUsers.Find(userId); if (user != null) { user.UserToken = string.Empty; loginName = user.LoginName; } db.SystemUserLoginfo.Add(new SystemUserLoginfo { IPAddress = HttpContext.Connection.RemoteIpAddress?.ToString(), LogTime = DateTime.Now, LogType = "登出", UserId = userId, UserName = user?.Name ?? User.GetUserName(), MACAddress = string.Empty }); db.SaveChanges(); } } catch (Exception ex) { LogFactory.GetLogger("用户登出").Error(ex); } } HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); if (SsoLoginConfig.SSO_Enable) { switch (loginName?.ToLowerInvariant()) { case "admin": case "supera": break; default: if (PS.Config.Company == CompanyEnum.天风) { return View("TianFengLogout"); } break; } } return RedirectToAction("Login"); } public ActionResult Captcha() { Request.Cookies.TryGetValue(_auth_login, out var guid); if (string.IsNullOrEmpty(guid) || guid.Length < 30) { return StatusCode((int)System.Net.HttpStatusCode.InternalServerError, "cookie无效"); } var key = _auth_login + guid; var value = Server.CacheProvider.Get(key)?.ToString(); if (value == null) { return StatusCode((int)System.Net.HttpStatusCode.InternalServerError, "页面停留超时"); } var info = _captcha.Generate(key); // 有多处验证码且过期时间不一样,可传第二个参数覆盖默认配置。 //var info = _captcha.Generate(id,120); var stream = new MemoryStream(info.Bytes); return File(stream, "image/jpeg"); } private static string Decode(string btoa) { var mod4 = btoa.Length % 4; if (mod4 > 0) { btoa += new string('=', 4 - mod4); } var data = Convert.FromBase64String(btoa); var strs = System.Text.Encoding.ASCII.GetString(data).Split(','); var arr = new char[strs.Length]; for (var i = 0; i < strs.Length; i++) { arr[i] = (char)int.Parse(strs[i]); } return new string(arr); } #region----天风证券单点登录---- /// /// 天风证券单点登录 /// public async Task TianFengSSO() { var serviceUrl = Url.Action("TianFengSSO", "account", null, Request.Scheme); var model = new SsoLogOnModel { SsoUrl = TianFengSSOHandler.GetSsoLoginUrl(serviceUrl) }; try { var ticket = Request.Query["ticket"]; if (string.IsNullOrWhiteSpace(ticket)) { return View(model.SetError("缺少单点登录令牌!")); } var userName = await new TianFengSSOHandler(_httpClientFactory).ValidateAsync(ticket, serviceUrl); var userToken = Guid.NewGuid().ToString("N"); SystemUser user = null; using (var db = Modules.DbContextFactory.GetErpBaseContext()) { user = db.SystemUsers.FirstOrDefault(n => n.LoginName == userName); if (user == null) { return View(model.SetError("找不到系统用户:" + userName)); } user.UserToken = userToken; db.SystemUserLoginfo.Add(new SystemUserLoginfo { IPAddress = HttpContext.Connection.RemoteIpAddress.ToString(), LogTime = DateTime.Now, LogType = "登入", UserId = user.Id, UserName = user.Name, LoginName = user.LoginName, MACAddress = "", LoadTime = DateTime.Now }); db.SaveChanges(); } //保存到cookie var identity = UserManager.CreateIdentity(user.Id.ToString(), user.Name, userToken); await HttpContext.SignInAsync(AuthHelper.CookieAuthType, new ClaimsPrincipal(identity)); var jwtToken = AuthHelper.CreateJwtToken(identity); Response.Cookies.Append("Access-Token", jwtToken, new CookieOptions { Domain = Request.Host.Host, MaxAge = TimeSpan.FromSeconds(AuthHelper.ExpireInSeconds) }); //保存到缓存 var userInfo = UserInfo.FromDbUser(user); Server.CacheProvider.Set("loginUser^" + user.Id, userInfo, TimeSpan.FromMinutes(30)); return RedirectToAction("Index", "Home"); } catch (Exception ex) { return View(model.SetError("单点登录出错:" + ex.ToString())); } } #endregion } }