近日在吉日的指导下为通用权限管理系统增加了IP和MAC访问控制功能。
该功能可以同时对
- 单个IP(例如192.168.0.1)
- 一段IP (192.168.0.1-192.168.0.10)
- 带通配符的IP(192.168.0.*)
- MAC地址(00-16-36-3f-95-98)
进行访问控制。
系统通过配置config.xml 文件打开和关闭IP访问控制功能,默认为不开启。
<!-- 是否开启IP限制-->
<add key="CheckIPAddress" value="False"/>
如果开启IP访问控制,编辑用户属性即可看到相应的操作按钮。
可以增加相应的IP地址和MAC地址,增加的地址是允许用户登录系统的地址。
用户的IP地址不在允许的范围内时,不允许登录。
如果MAC地址不在允许的范围内时,不允许登录。
对用户进行IP访问控制的业务逻辑主要通过BaseUserManager类中的LogOn方法实现。代码如下:
#region public BaseUserInfo LogOn(string userName, string password, string ipAddress, string macAddress, out string statusCode) 进行登录操作
/// <summary>
/// 进行登录操作
/// 日志进行改进
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="password">密码</param>
/// <param name="ipAddress">IP地址</param>
/// <param name="macAddress">MAC地址</param>
/// <returns>用户类</returns>
public BaseUserInfo LogOn(string userName, string password, string ipAddress, string macAddress, out string statusCode)
{
BaseUserInfo userInfo = null;
// 01. 查询数据库中的用户数据?只查询未被删除的
string[] names = new string[] { BaseUserTable.FieldDeletionStateCode, BaseUserTable.FieldUserName };
Object[] values = new Object[] { 0, userName };
DataTable dataTable = this.GetDT(names, values);
// 02. 系统是否采用了密码加密策略?
string encryptPassword = string.Empty;
if (BaseSystemInfo.ServerEncryptPassword)
{
password = this.EncryptUserPassword(password);
}
// 03. 默认为用户没有找到状态,查找用户
// statusCode = StatusCode.UserNotFound.ToString();
// 这是为了达到安全要求,不能提示用户未找到,那容易让别人猜测到帐户
statusCode = StatusCode.ErrorLogOn.ToString();
BaseUserEntity userEntity = null;
// 04. 判断密码,是否允许登录,是否离职是否正确
foreach (DataRow dataRow in dataTable.Rows)
{
userEntity = new BaseUserEntity(dataRow);
if (!string.IsNullOrEmpty(userEntity.AuditStatus) && userEntity.AuditStatus.EndsWith(AuditStatus.WaitForAudit.ToString()))
{
statusCode = AuditStatus.WaitForAudit.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userName, UserInfo.IPAddress, "用户登录被拒,用户审核中。");
return userInfo;
}
// 用户是否有效的
if (userEntity.Enabled == 0)
{
statusCode = StatusCode.LogOnDeny.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userName, UserInfo.IPAddress, "用户被锁定,登录被拒绝,请联系系统管理员。");
return userInfo;
}
// 用户是否有效的
if (userEntity.Enabled == -1)
{
statusCode = StatusCode.UserNotActive.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userName, UserInfo.IPAddress, "用户未被激活,请及时激活用户帐户。");
return userInfo;
}
// 05. 允许登录时间是否有限制
if (userEntity.AllowEndTime != null)
{
userEntity.AllowEndTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, userEntity.AllowEndTime.Value.Hour, userEntity.AllowEndTime.Value.Minute, userEntity.AllowEndTime.Value.Second);
}
if (userEntity.AllowStartTime != null)
{
userEntity.AllowStartTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, userEntity.AllowStartTime.Value.Hour, userEntity.AllowStartTime.Value.Minute, userEntity.AllowStartTime.Value.Second);
if (DateTime.Now < userEntity.AllowStartTime)
{
statusCode = StatusCode.UserLocked.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userName, UserInfo.IPAddress, "用户被锁定,登录被拒绝,不得早于:" + userEntity.AllowStartTime.Value.ToString("HH:mm"));
return userInfo;
}
}
if (userEntity.AllowEndTime != null)
{
if (DateTime.Now > userEntity.AllowEndTime)
{
statusCode = StatusCode.UserLocked.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userName, UserInfo.IPAddress, "用户被锁定,登录被拒绝,不得晚于:" + userEntity.AllowEndTime.Value.ToString("HH:mm"));
return userInfo;
}
}
// 06. 锁定日期是否有限制
if (userEntity.LockStartDate != null)
{
if (DateTime.Now > userEntity.LockStartDate)
{
if (userEntity.LockEndDate == null || DateTime.Now < userEntity.LockEndDate)
{
statusCode = StatusCode.UserLocked.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userName, UserInfo.IPAddress, "用户被锁定,登录被拒绝,锁定开始日期:" + userEntity.LockStartDate.Value.ToString("yyyy-MM-dd"));
return userInfo;
}
}
}
if (userEntity.LockEndDate != null)
{
if (DateTime.Now < userEntity.LockEndDate)
{
statusCode = StatusCode.UserLocked.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userName, UserInfo.IPAddress, "用户被锁定,登录被拒绝,锁定结束日期:" + userEntity.LockEndDate.Value.ToString("yyyy-MM-dd"));
return userInfo;
}
}
// 07. 是否检查用户IP地址,是否进行访问限制?, 管理员不检查IP
if (BaseSystemInfo.CheckIPAddress && !this.IsAdministrator(userEntity.Id.ToString()))
{
if (!string.IsNullOrEmpty(ipAddress) && !this.CheckIPAddress(ipAddress, userEntity.Id.ToString()))
{
statusCode = StatusCode.ErrorIPAddress.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, ipAddress, UserInfo.IPAddress, "IPAddress 不正确。");
return userInfo;
}
}
// 08. 是否检查用户的网卡Mac地址,是否进行访问限制?管理员不检查
if (BaseSystemInfo.CheckIPAddress && !this.IsAdministrator(userEntity.Id.ToString()))
{
if (!string.IsNullOrEmpty(macAddress) && !this.CheckMacAddress(macAddress, userEntity.Id.ToString()))
{
statusCode = StatusCode.ErrorMacAddress.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, macAddress, UserInfo.IPAddress, "MacAddress 不正确。");
return userInfo;
}
}
// 09. 更新IP地址,更新MAC地址
this.SetProperty(userEntity.Id, BaseUserTable.FieldIPAddress, ipAddress);
this.SetProperty(userEntity.Id, BaseUserTable.FieldMACAddress, macAddress);
// 10. 只允许登录一次,需要检查是否自己重新登录了,或者自己扮演自己了
if ((UserInfo != null) && (!UserInfo.Id.Equals(userEntity.Id.ToString())))
{
if (BaseSystemInfo.CheckOnLine)
{
if (userEntity.UserOnLine > 0)
{
statusCode = StatusCode.ErrorOnLine.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userName, UserInfo.IPAddress, "用户已在线,不允许重复登录。");
return userInfo;
}
}
}
// 11. 密码是否正确(null 与空看成是相等的)
if (!(string.IsNullOrEmpty(userEntity.UserPassword) && string.IsNullOrEmpty(password)))
{
bool userPasswordOK = true;
// 用户密码是空的
if (string.IsNullOrEmpty(userEntity.UserPassword))
{
// 但是输入了不为空的密码
if (!string.IsNullOrEmpty(password))
{
userPasswordOK = false;
}
}
else
{
// 用户的密码不为空,但是用户是输入了密码
if (string.IsNullOrEmpty(password))
{
userPasswordOK = false;
}
else
{
// 再判断用户的密码与输入的是否相同
userPasswordOK = userEntity.UserPassword.Equals(password);
}
}
// 用户的密码不相等
if (!userPasswordOK)
{
BaseLogManager.Instance.Add(DbHelper, userEntity.Id.ToString(), userEntity.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userEntity.RealName, ipAddress, "密码错误,登录被拒绝。");
statusCode = StatusCode.PasswordError.ToString();
return userInfo;
}
}
// 12. 是否检查同时在线用户数量,是否超过了软件购买的许可?
if (BaseSystemInfo.OnLineLimit > 0)
{
if (this.CheckOnLineLimit())
{
statusCode = StatusCode.ErrorOnLineLimit.ToString();
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userName, UserInfo.IPAddress, "已超出用户在线数量上限" + BaseSystemInfo.OnLineLimit.ToString());
return userInfo;
}
}
// 可以正常登录了
statusCode = StatusCode.OK.ToString();
// 13. 登录、重新登录、扮演时的在线状态进行更新
this.ChangeOnLine(userEntity.Id.ToString());
userInfo = this.ConvertToUserInfo(userEntity);
// 获得员工的信息
if (userEntity.IsStaff == 1)
{
/*
BaseStaffManager staffManager = new BaseStaffManager(DbHelper, UserInfo);
// 这里需要按 员工的用户ID来进行查找对应的员工-用户关系
BaseStaffEntity staffEntity = new BaseStaffEntity(staffManager.GetDT(BaseStaffTable.FieldUserId, userEntity.Id));
if (staffEntity.Id > 0)
{
userInfo = staffManager.ConvertToUserInfo(staffEntity, userInfo);
}
*/
}
userInfo.IPAddress = ipAddress;
userInfo.MACAddress = macAddress;
userInfo.Password = password;
// 数据找到了,就可以退出循环了
break;
}
// 14. 记录系统访问日志
if (statusCode == StatusCode.UserNotFound.ToString())
{
BaseLogManager.Instance.Add(DbHelper, userName, UserInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userName, UserInfo.IPAddress, "没有找到用户名,登录被拒绝。");
}
if (statusCode == StatusCode.OK.ToString())
{
BaseLogManager.Instance.Add(DbHelper, userEntity.Id.ToString(), userInfo.RealName, "LogOn", AppMessage.BaseUserManager, "LogOn", AppMessage.BaseUserManager_LogOn, userEntity.RealName, UserInfo.IPAddress, AppMessage.BaseUserManager_LogOnSuccess);
userInfo.OpenId = this.UpdateVisitDate(userEntity.Id.ToString(), true);
}
return userInfo;
}
#endregion
以下是检查IP地址和MAC地址的方法。
#region private bool CheckIPAddress(string ipAddress, string userId) 检查用户IP地址
/// <summary>
/// 检查用户IP地址
/// </summary>
/// <param name="ipAddress">IP地址</param>
/// <returns>是否符合限制</returns>
private bool CheckIPAddress(string ipAddress, string userId)
{
bool returnValue = false;
string[] names = { BaseParameterTable.FieldParameterId, BaseParameterTable.FieldCategoryId, BaseParameterTable.FieldEnabled };
Object[] values = { userId, "IPAddress", 1 };
DataTable dt = DbLogic.GetDT(this.DbHelper, BaseParameterTable.TableName, names, values);
if (dt.Rows.Count > 0)
{
string parameterCode = string.Empty;
string parameterCotent = string.Empty;
for (int i = 0; i < dt.Rows.Count; i++)
{
parameterCode = dt.Rows[i][BaseParameterTable.FieldParameterCode].ToString();
parameterCotent = dt.Rows[i][BaseParameterTable.FieldParameterContent].ToString();
switch (parameterCode)
{
//匹配单个IP
case "Single":
returnValue = CheckSingleIPAddress(ipAddress, parameterCotent);
break;
//匹配ip地址段
case "Range":
returnValue = CheckIPAddressWithRange(ipAddress, parameterCotent);
break;
//匹配带掩码的地址段
case "Mask":
returnValue = CheckIPAddressWithMask(ipAddress, parameterCotent);
break;
}
if (returnValue) break;
}
}
return returnValue;
}
/// <summary>
/// 检查是否匹配单个IP
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="sourceIp"></param>
/// <returns></returns>
private bool CheckSingleIPAddress(string ipAddress, string sourceIp)
{
return ipAddress.Equals(sourceIp);
}
/// <summary>
/// 检查是否匹配地址段
/// </summary>
/// <param name="ipAddress">192.168.0.8</param>
/// <param name="ipRange">192.168.0.1-192.168.0.10</param>
/// <returns></returns>
private bool CheckIPAddressWithRange(string ipAddress, string ipRange)
{
//先判断符合192.168.0.1-192.168.0.10 的正则表达式
//在判断ipAddress是否有效
string startIp = ipRange.Split('-')[0];
string endIp = ipRange.Split('-')[1];
//如果大于等于 startip 或者 小于等于endip
if (CompareIp(ipAddress, startIp) == 2 && CompareIp(ipAddress, endIp) == 0 || CompareIp(ipAddress, startIp) == 1 || CompareIp(ipAddress, endIp) == 1)
{
return true;
}
return false;
}
/// <summary>
/// 比较两个IP地址,比较前可以先判断是否是IP地址
/// </summary>
/// <param name="ip1"></param>
/// <param name="ip2"></param>
/// <returns>1:相等; 0:ip1小于ip2 ; 2:ip1大于ip2;-1 不符合ip正则表达式 </returns>
public int CompareIp(string ip1, string ip2)
{
//if (!IsIP(ip1) || !IsIP(ip2))
//{
// return -1;
//}
String[] arr1 = ip1.Split('.');
String[] arr2 = ip2.Split('.');
for (int i = 0; i < arr1.Length; i++)
{
int a1 = int.Parse(arr1[i]);
int a2 = int.Parse(arr2[i]);
if (a1 > a2)
{
return 2;
}
else if (a1 < a2)
{
return 0;
}
}
return 1;
}
/// <summary>
/// 检查是否匹配带通配符的IP地址
/// </summary>
/// <param name="ipAddress">192.168.1.1</param>
/// <param name="ipWithMask">192.168.1.*</param>
/// <returns></returns>
private bool CheckIPAddressWithMask(string ipAddress, string ipWithMask)
{
//先判断是否符合192.168.1.*
//然后判断
string[] arr1 = ipAddress.Split('.');
string[] arr2 = ipWithMask.Split('.');
for (int i = 0; i < arr1.Length; i++)
{
if (!(arr2[i].Equals("*") || arr1[i].Equals(arr2[i])))
{
return false;
}
}
return true;
}
#endregion
private bool CheckIPAddress(string[] ipAddress, string userId)
{
bool returnValue = false;
for (int i = 0; i < ipAddress.Length; i++)
{
if (this.CheckIPAddress(ipAddress[i], userId))
{
returnValue = true;
break;
}
}
return returnValue;
}
#region private bool CheckMacAddress(string macAddress, string userId) 检查用户的网卡Mac地址
/// <summary>
/// 检查用户的网卡Mac地址
/// </summary>
/// <param name="macAddress">Mac地址</param>
/// <returns>是否符合限制</returns>
private bool CheckMacAddress(string macAddress, string userId)
{
bool returnValue = false;
string[] names = { BaseParameterTable.FieldParameterId, BaseParameterTable.FieldCategoryId, BaseParameterTable.FieldEnabled };
Object[] values = { userId, "MacAddress", 1 };
DataTable dt = DbLogic.GetDT(this.DbHelper, BaseParameterTable.TableName, names, values);
if (dt.Rows.Count > 0)
{
string parameterCode = string.Empty;
string parameterCotent = string.Empty;
for (int i = 0; i < dt.Rows.Count; i++)
{
parameterCode = dt.Rows[i][BaseParameterTable.FieldParameterCode].ToString();
parameterCotent = dt.Rows[i][BaseParameterTable.FieldParameterContent].ToString();
returnValue = (macAddress.ToLower()).Equals(parameterCotent.ToLower());//简单格式化一下
if (returnValue) break;
}
}
return returnValue;
}
#endregion
private bool CheckMacAddress(string[] macAddress, string userId)
{
bool returnValue = false;
for (int i = 0; i < macAddress.Length; i++)
{
if (this.CheckMacAddress(macAddress[i], userId))
{
returnValue = true;
break;
}
}
return returnValue;
}
另外,功能实现的过程中优化了获取IP地址和MAC地址的方法。代码如下
/// <summary>
/// 获取当前使用的IPV4地址
/// </summary>
/// <returns></returns>
public static string GetIPAddress()
{
string ipAddress = string.Empty;
System.Net.IPHostEntry ipHostEntrys = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName());
List<string> ipList = GetIPAddressList();
foreach (string ip in ipList)
{
ipAddress = ip.ToString();
break;
}
return ipAddress;
}
/// <summary>
/// 获取IPv4地址列表,注意优先级高的放在了后面
/// </summary>
/// <returns></returns>
public static List<string> GetIPAddressList()
{
List<string> ipAddressList = new List<string>();
IPHostEntry ipHostEntrys = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName());
foreach (IPAddress ip in ipHostEntrys.AddressList)
{
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
ipAddressList.Add(ip.ToString());
}
}
return ipAddressList;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public static string GetMacAddress()
{
string macAddress = string.Empty;
List<string> macAddressList = GetMacAddressList();
foreach (string mac in macAddressList)
{
if (!string.IsNullOrEmpty(mac))
{
macAddress = mac.ToString();
//格式化
macAddress = string.Format("{0}-{1}-{2}-{3}-{4}-{5}",
macAddress.Substring(0, 2),
macAddress.Substring(2, 2),
macAddress.Substring(4, 2),
macAddress.Substring(6, 2),
macAddress.Substring(8, 2),
macAddress.Substring(10, 2));
break;
}
}
return macAddress;
}
/// <summary>
/// 获取MAC地址列表,注意优先级高的放在了后面,注意优先级高的放在了后面
/// </summary>
/// <returns></returns>
public static List<string> GetMacAddressList()
{
List<string> macAddressList = new List<string>();
NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface ni in networkInterfaces)
{
//过滤掉虚拟网卡、移动网卡和Loopback
if (!ni.Description.Contains("WiFi") && !ni.Description.Contains("Loopback") && !ni.Description.Contains("VMware") && ni.OperationalStatus == OperationalStatus.Up)
{
macAddressList.Add(ni.GetPhysicalAddress().ToString());
}
}
return macAddressList;
}
有待于进一步完善的地方:
1、我在测试的过程中,当同时启用无线网络和有线网络时,获取的IP到底是有线网络的IP还是无线网络的IP呢?? 以前问过群里说是用route print 命令 查看metric小的优先级高。可是有一次测试时依然获得了metric大的IP。
2、管理员手工增加IP地址或者MAC地址 时的工作量还是比较大的。我们知道在系统登录的时候保存了用户的IP地址和MAC,这样话如果增加一功能“使用最后一次登录的IP和MAC”来增加地址的话,能减轻管理员的负担。
谢谢大家指点。