350 lines
12 KiB
C#
350 lines
12 KiB
C#
using System;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Security.Cryptography;
|
||
using System.ServiceModel;
|
||
using System.ServiceModel.Channels;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using Aitex.Common.Util;
|
||
using Aitex.Core.Account;
|
||
using Aitex.Core.RT.Event;
|
||
using Aitex.Core.RT.Log;
|
||
using Aitex.Core.Util;
|
||
using Aitex.Core.Utilities;
|
||
using Aitex.Core.WCF;
|
||
using MECF.Framework.Common.Account.Extends;
|
||
using MECF.Framework.Common.Equipment;
|
||
|
||
namespace MECF.Framework.Common.Account
|
||
{
|
||
/// <summary>
|
||
/// 账号管理器。
|
||
/// </summary>
|
||
public class AccountExManager : Singleton<AccountExManager>
|
||
{
|
||
#region Variables
|
||
|
||
private readonly string _scAccountTemplateFile = PathManager.GetCfgDir() + "Account//Account.xml";
|
||
private readonly string _scAccountLocalFile = PathManager.GetCfgDir() + "Account//_AccountEx.xml";
|
||
|
||
// 旧的_Account.xml文件路径,为兼容已有的账户配置。
|
||
private readonly string _oldAccountXmlFile = PathManager.GetCfgDir() + "Account//_Account.xml";
|
||
|
||
private readonly SemaphoreSlim _syncRoot = new SemaphoreSlim(1, 1);
|
||
|
||
#endregion
|
||
|
||
#region Constructors
|
||
|
||
public AccountExManager()
|
||
{
|
||
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
|
||
/// <summary>
|
||
/// 返回角色加载器。
|
||
/// </summary>
|
||
public RoleAccountLoader RoleAccountLoader { get; private set; }
|
||
|
||
#endregion
|
||
|
||
#region Methods
|
||
|
||
/// <summary>
|
||
/// 初始化当前对象。
|
||
/// </summary>
|
||
/// <param name="enableService"></param>
|
||
/// <exception cref="ApplicationException"></exception>
|
||
public void Initialize(bool enableService)
|
||
{
|
||
if (!File.Exists(_scAccountLocalFile))
|
||
{
|
||
if (File.Exists(_oldAccountXmlFile))
|
||
{
|
||
// 如果已存在_Account.xml,则从_Account.xml创建_AccountEx.xml文件。
|
||
File.Copy(_oldAccountXmlFile, _scAccountLocalFile);
|
||
Thread.Sleep(10);
|
||
}
|
||
else if (File.Exists(_scAccountTemplateFile))
|
||
{
|
||
File.Copy(_scAccountTemplateFile, _scAccountLocalFile);
|
||
Thread.Sleep(10);
|
||
}
|
||
else
|
||
{
|
||
// 模板文件不存在
|
||
throw new ApplicationException("Can not initialize account configuration file, " +
|
||
_scAccountTemplateFile);
|
||
}
|
||
}
|
||
|
||
// 默认不支持多用户登录。
|
||
CredentialManager.Instance.Initialize(false);
|
||
|
||
RoleAccountLoader = new RoleAccountLoader(_scAccountLocalFile);
|
||
RoleAccountLoader.Load();
|
||
|
||
if (enableService)
|
||
{
|
||
Singleton<WcfServiceManager>.Instance.Initialize(new Type[1] { typeof(AccountService) });
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取WCF客户端IP信息。
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private static RemoteEndpointMessageProperty GetCurrentWCFClientEndPoint()
|
||
{
|
||
var context = OperationContext.Current;
|
||
var prop = context.IncomingMessageProperties;
|
||
var endpoint =
|
||
prop[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
|
||
return endpoint;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 等待已登录的用户确认是否允许新的登录请求。
|
||
/// </summary>
|
||
/// <param name="userLoggedIn">已登录用户的凭据。</param>
|
||
/// <param name="userRequesting">新用户的登录凭据。</param>
|
||
/// <returns></returns>
|
||
private static async Task<LoginRequestResults> RequestAndWaitLoginConfirmation(Credential userLoggedIn,
|
||
Credential userRequesting)
|
||
{
|
||
// 等待已登录用户响应,返回结果;
|
||
// 1. Rejected,拒绝新的登录请求
|
||
// 2. Logged Out,主动注销,允许新的登录请求
|
||
// 3. Timeout,超时没有响应,保持原用户的登录凭据
|
||
return await Task.Run(async () =>
|
||
{
|
||
// 通知已登录的用户,其它客户端使用该用户名登录,是否允许登录
|
||
var ct = userRequesting.LoginRequestCancellationTokenSource.Token;
|
||
|
||
// 等待已登录的凭据被移除(客户端注销)
|
||
var sw = new Stopwatch();
|
||
sw.Start();
|
||
|
||
while (true)
|
||
{
|
||
switch (userRequesting.State)
|
||
{
|
||
case CredentialState.Confirmed:
|
||
// 客户端已注销,可以放行新的凭据
|
||
CredentialManager.Instance.Grant(userRequesting);
|
||
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn,
|
||
userLoggedIn.AccountInfo.LoginName);
|
||
return await Task.FromResult(LoginRequestResults.Confirmed);
|
||
|
||
case CredentialState.Reject or CredentialState.RequestCanceled:
|
||
if(userRequesting.State == CredentialState.Reject)
|
||
// 已登录用户拒绝了登录请求
|
||
EV.PostInfoLog(ModuleName.System.ToString(),
|
||
$"User {userRequesting.AccountInfo.LoginName}'s login request from {userRequesting.LoginIP}:{userRequesting.LoginPort} is rejected");
|
||
|
||
return await Task.FromResult(LoginRequestResults.Rejected);
|
||
|
||
case CredentialState.Requesting:
|
||
case CredentialState.Alive:
|
||
|
||
break;
|
||
}
|
||
|
||
|
||
Thread.Sleep(200);
|
||
if (ct.IsCancellationRequested)
|
||
break;
|
||
|
||
// 等待指定的时间,然后向申请端发送超时
|
||
if (sw.Elapsed.TotalSeconds > CredentialManager.REQ_LOGIN_CRED_LIFT_TIME_SEC)
|
||
break;
|
||
|
||
}
|
||
|
||
// 等待确认超时
|
||
return LoginRequestResults.Timeout;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 登录。
|
||
/// </summary>
|
||
/// <param name="userName">用户名</param>
|
||
/// <param name="password">密码</param>
|
||
/// <param name="roleID">角色ID</param>
|
||
/// <param name="clientInfo">发起登录请求的客户端信息</param>
|
||
/// <returns><see cref="LoginRequestResults"/></returns>
|
||
/// <exception cref="TimeoutException"></exception>
|
||
internal async Task<LoginResult> Login(string userName, string password, string roleID,
|
||
LoginClientInfo clientInfo)
|
||
{
|
||
var endPoint = GetCurrentWCFClientEndPoint();
|
||
|
||
try
|
||
{
|
||
var accountList = RoleAccountLoader.GetAccounts();
|
||
|
||
var matchedAccount = accountList.FirstOrDefault(x => x.LoginName == userName);
|
||
if (matchedAccount == null)
|
||
// 用户不存在
|
||
return await Task.FromResult(new LoginResult(LoginRequestResults.NoMatchUser));
|
||
|
||
if (matchedAccount.Password != password)
|
||
// 密码错误
|
||
return await Task.FromResult(new LoginResult(LoginRequestResults.WrongPwd));
|
||
|
||
// 查找当前用户的登录角色是否存在
|
||
var matchedRole = matchedAccount.RoleIDs.FirstOrDefault(x => x == roleID);
|
||
if (matchedRole == null)
|
||
// 找不到角色
|
||
return await Task.FromResult(new LoginResult(LoginRequestResults.NoMatchRole));
|
||
|
||
// 创建新凭据
|
||
var requestingCred = new Credential(CredentialManager.GenerateToken(), matchedAccount)
|
||
{
|
||
LoginIP = endPoint.Address,
|
||
LoginPort = endPoint.Port,
|
||
ClientInfo = clientInfo,
|
||
RoleID = roleID
|
||
};
|
||
|
||
|
||
if (CredentialManager.Instance.LoggedInCount == 0 && CredentialManager.Instance.LoginRequestingCount == 0)
|
||
{
|
||
// 添加凭据到登录请求列表,Grant时需要请求列表中存在此凭据。
|
||
CredentialManager.Instance.AddRequestingList(requestingCred);
|
||
|
||
// 没有用户登录,直接发放凭据
|
||
CredentialManager.Instance.Grant(requestingCred);
|
||
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn, $"{userName}@{requestingCred.LoginIP}:{requestingCred.LoginPort}");
|
||
return await Task.FromResult(new LoginResult(LoginRequestResults.Confirmed, requestingCred));
|
||
}
|
||
|
||
if (CredentialManager.Instance.IsSupportMultiUserLogin)
|
||
{
|
||
// 支持多用户同时登录
|
||
|
||
// 如果当前用户名正在请求登录,则拒绝发起新的请求。
|
||
if (CredentialManager.Instance.GetRequestingCredential(userName) != null)
|
||
return await Task.FromResult(new LoginResult(LoginRequestResults.RequstingLogin));
|
||
|
||
// 添加凭据到登录请求列表,Grant时需要请求列表中存在此凭据。
|
||
CredentialManager.Instance.AddRequestingList(requestingCred);
|
||
|
||
// 查找当前请求的用户是否已经登录
|
||
var loggedCred = CredentialManager.Instance.GetCredential(userName);
|
||
if (loggedCred == null)
|
||
{
|
||
// 用户未登录,直接放行凭据
|
||
CredentialManager.Instance.Grant(requestingCred);
|
||
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn, $"{userName}@{requestingCred.LoginIP}:{requestingCred.LoginPort}");
|
||
return await Task.FromResult(new LoginResult(LoginRequestResults.Confirmed, requestingCred));
|
||
}
|
||
|
||
// 否则等待已登录的Session确认
|
||
var ret = await RequestAndWaitLoginConfirmation(loggedCred, requestingCred);
|
||
return ret == LoginRequestResults.Confirmed ? new LoginResult(ret, requestingCred) : new LoginResult(ret);
|
||
|
||
}
|
||
else
|
||
{
|
||
// 仅支持单一用户登录
|
||
if (CredentialManager.Instance.LoggedInCount > 1)
|
||
{
|
||
EV.PostWarningLog(ModuleName.System.ToString(),
|
||
$"{nameof(CredentialManager)} does not support multiple users login but more than one credentials found.");
|
||
}
|
||
|
||
// 如果有正在请求登录,则拒绝发起新的请求。
|
||
if (CredentialManager.Instance.LoginRequestingCount > 0)
|
||
return await Task.FromResult(new LoginResult(LoginRequestResults.RequstingLogin));
|
||
|
||
// 添加凭据到登录请求列表,Grant时需要请求列表中存在此凭据。
|
||
CredentialManager.Instance.AddRequestingList(requestingCred);
|
||
|
||
// 已登录的用户凭据
|
||
var loggedCred = CredentialManager.Instance.GetCredential();
|
||
|
||
var ret = await RequestAndWaitLoginConfirmation(loggedCred, requestingCred);
|
||
return ret == LoginRequestResults.Confirmed ? new LoginResult(ret, requestingCred) : new LoginResult(ret);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LOG.Error(ex.Message, ex);
|
||
return await Task.FromResult(new LoginResult(LoginRequestResults.Error));
|
||
}
|
||
finally
|
||
{
|
||
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 登录请求发起端取消登录请求。
|
||
/// </summary>
|
||
/// <param name="userName"></param>
|
||
internal void CancelLoginRequest(string userName)
|
||
{
|
||
CredentialManager.Instance.Cancel(userName);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确认登录请求。
|
||
/// </summary>
|
||
/// <param name="userName"></param>
|
||
internal void ConfirmedLoginRequest(string userName)
|
||
{
|
||
CredentialManager.Instance.Accept(userName);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 拒绝登录请求。
|
||
/// </summary>
|
||
/// <param name="userName"></param>
|
||
internal void RejectLoginRequest(string userName)
|
||
{
|
||
CredentialManager.Instance.Reject(userName);
|
||
}
|
||
|
||
internal void Logout(Guid myToken)
|
||
{
|
||
var loginCred = CredentialManager.Instance.GetCredential(myToken);
|
||
if (loginCred != null)
|
||
{
|
||
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedOff,
|
||
$"{loginCred.AccountInfo.LoginName}@{loginCred.LoginIP}:{loginCred.LoginPort}");
|
||
}
|
||
|
||
CredentialManager.Instance.Remove(myToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 校验指定账户密码是否正确。
|
||
/// </summary>
|
||
/// <param name="accountId">账号ID</param>
|
||
/// <param name="passwordMD5">MD5加密后的密码</param>
|
||
/// <returns></returns>
|
||
internal bool CheckPassword(string accountId, string passwordMD5)
|
||
{
|
||
var account = RoleAccountLoader.GetAccounts().FirstOrDefault(x => x.UserId == accountId);
|
||
if (account == null)
|
||
{
|
||
LOG.Error($"CheckPassword Unable to find the account with ID {accountId}");
|
||
return false;
|
||
}
|
||
|
||
return account.Password == passwordMD5;
|
||
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|