通用权限管理系统IP、MAC访问控制的实现
近日在吉日的指导下为通用权限管理系统增加了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”来增加地址的话,能减轻管理员的负担。
谢谢大家指点。