Files
zszq-trs/YLErpWeb/Controllers/AccountController.cs
T

424 lines
15 KiB
C#

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);
}
/// <summary>
/// 登录错误
/// </summary>
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<IActionResult> 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----天风证券单点登录----
/// <summary>
/// 天风证券单点登录
/// </summary>
public async Task<ActionResult> 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
}
}