近期有個需求,需要獲取客戶端Mac地址作為白名單驗證的依據。使用.net,B/S架構。先百度找了一些獲取mac地址的方法,
using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.IO; using System.Net; using System.Configuration; using System.Web; using System.Security.Cryptography; using System.Data; using System.Web.UI.WebControls; using System.Management; using System.Diagnostics; using System.Net.NetworkInformation; using System.Runtime.InteropServices; namespace Qianxun.Common { public class MacAddress { #region 獲取mac地址的一些方法 #region 1 通過IPConfig命令讀取MAC地址(選用) /// <summary> /// 根據擷取ipconfig /all命令的輸出流獲取網路卡Mac /// </summary> /// <returns></returns> public static List<string> GetMacByIPConfig() { List<string> macs = new List<string>(); try { ProcessStartInfo startInfo = new ProcessStartInfo("ipconfig", "/all"); startInfo.UseShellExecute = false; startInfo.RedirectStandardInput = true; startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; startInfo.CreateNoWindow = true; Process p = Process.Start(startInfo); // 擷取輸出流 StreamReader reader = p.StandardOutput; string line = reader.ReadLine(); while (!reader.EndOfStream) { if (!string.IsNullOrEmpty(line)) { line = line.Trim(); if (line.StartsWith("Physical Address") || line.StartsWith("實體地址")) { macs.Add(line.Substring(line.IndexOf(":") + 1, line.Length - line.IndexOf(":") - 1)); break; } } line = reader.ReadLine(); } // 等待程式執行完退出程式 p.WaitForExit(); p.Close(); reader.Close(); } catch (Exception ex) { } return macs; } #endregion #region 2 通過WMI讀取MAC地址 /// <summary> /// 通過WMI讀取系統資訊裡的網路卡MAC /// 該方法依賴WMI的系統服務,該服務一般不會被關閉;但如果系統服務缺失或者出現問題,該方法無法取得MAC地址。 /// </summary> /// <returns></returns> public static List<string> GetMacByWMI() { List<string> macs = new List<string>(); try { string mac = ""; ManagementClass mc = new ManagementClass(" Win32_NetworkAdapterConfiguration "); ManagementObjectCollection moc = mc.GetInstances(); foreach (ManagementObject mo in moc) { if ((bool)mo[" IPEnabled "]) { mac = mo[" MacAddress "].ToString(); macs.Add(mac); } } moc = null; mc = null; } catch(Exception ex) { } return macs; } #endregion #region 3 通過NetworkInterface讀取MAC地址:1)如果當前的網路卡是禁用狀態(硬體處於硬關閉狀態),取不到該網路卡的MAC地址,(您可以通過禁用網路卡進行試驗)。2)如果當前啟用了多個網路卡,最先返回的地址是最近啟用的網路連線的資訊 // 返回描述本地計算機上的網路介面的物件(網路介面也稱為網路介面卡)。 public static NetworkInterface[] NetCardInfo() { return NetworkInterface.GetAllNetworkInterfaces(); } /// <summary> /// 通過NetworkInterface讀取網路卡Mac /// </summary> /// <returns></returns> public static List<string> GetMacByNetworkInterface() { List<string> macs = new List<string>(); try { NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface ni in interfaces) { macs.Add(ni.GetPhysicalAddress().ToString()); } } catch (Exception ex) { } return macs; } #endregion #region 4 通過SendARP讀取MAC地址 /// <summary> /// 通過SendARP獲取網路卡Mac /// 網路被禁用或未接入網路(如沒插網線)時此方法失靈 /// </summary> /// <param name="remoteIP"></param> /// <returns></returns> public static string GetMacBySendARP(string remoteIP) { StringBuilder macAddress = new StringBuilder(); try { Int32 remote = inet_addr(remoteIP); Int64 macInfo = new Int64(); Int32 length = 6; SendARP(remote, 0, ref macInfo, ref length); string temp = Convert.ToString(macInfo, 16).PadLeft(12, '0').ToUpper(); int x = 12; for (int i = 0; i < 6; i++) { if (i == 5) { macAddress.Append(temp.Substring(x - 2, 2)); } else { macAddress.Append(temp.Substring(x - 2, 2) + " - "); } x -= 2; } return macAddress.ToString(); } catch { return macAddress.ToString(); } } [DllImport(" Iphlpapi.dll ")] private static extern int SendARP(Int32 dest, Int32 host, ref Int64 mac, ref Int32 length); [DllImport(" Ws2_32.dll ")] private static extern Int32 inet_addr(string ip); #endregion #region 5 從登錄檔讀取MAC地址 //常規使用者可通過讀取登錄檔項Windows Genuine Advantage獲取到物理網路卡地址。 //1)如果登錄檔項被修改,則無法取得該MAC地址 //HKEY_LOCAL_MACHINE\Software\Microsoft\Windows Genuine Advantage #endregion #endregion } }
測試時猛然發現多個客戶端獲取到的mac地址都是一樣的,驚覺獲取的是服務端的Mac地址,服務端語言獲取服務端Mac,那麼我需要獲取客戶端Mac,應該使用客戶端語言,最先想到的是js(因為有碰到過客戶端ip和服務端ip的問題,這裡倒也能想清楚)。百度了一圈,mac地址需要ActiveX指令碼支援,
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ie瀏覽器獲取客戶端Mac地址</title> </head> <body> <object classid="CLSID:76A64158-CB41-11D1-8B02-00600806D9B6" id="locator" style="display: none; visibility: hidden"></object> <object classid="CLSID:75718C9A-F029-11d1-A1AC-00C04FB6C223" id="foo" style="display: none; visibility: hidden"></object> <form name="myForm"> <br /> MAC地址:<input type="text" name="macAddress"> </form> </body> </html> <script language="javascript"> var sMacAddr = ""; var sIPAddr = ""; var sDNSName = ""; var service = locator.ConnectServer(); service.Security_.ImpersonationLevel = 3; service.InstancesOfAsync(foo, 'Win32_NetworkAdapterConfiguration'); </script> <script for="foo" event="OnObjectReady(objObject,objAsyncContext)" language="JScript"> if (objObject.IPEnabled != null && objObject.IPEnabled != "undefined" && objObject.IPEnabled == true) { if (objObject.IPEnabled && objObject.IPAddress(0) != null && objObject.IPAddress(0) != "undefined") sIPAddr = objObject.IPAddress(0); if (objObject.MACAddress != null && objObject.MACAddress != "undefined") sMacAddr = objObject.MACAddress; if (objObject.DNSHostName != null && objObject.DNSHostName != "undefined") sDNSName = objObject.DNSHostName; } </script> <script for="foo" event="OnCompleted(hResult,pErrorObject, pAsyncContext)" language="JScript"> myForm.macAddress.value = sMacAddr; </script> <script> //判斷瀏覽器是否支援ActiveX控制元件 //if (window.ActiveXObject) { //支援-通過ActiveXObject的一個新例項來建立XMLHttpRequest物件 var WshShell = new ActiveXObject("WScript.Shell"); //} //else { // $("#tips").html("請使用ie並將本系統域名加入受信任的站點,並在internet選項-受信任的站點中開啟ActiveX外掛和指令碼的相關許可權"); //} </script>
而這個東西僅支援ie,但是客戶系統對ie相容性不好,如果要調整,工作量巨大,那麼就只能讓其他瀏覽器比如chorme去相容ActiveX。
1.首先找到chorme for ActiveX擴充套件外掛,試了好幾個版本,也依然無法獲取到mac地址,不知道是我的使用方式有問題,還是這個方法已經失效。(chorme已經升級到當前最新版本 96.0.4664.45(正式版本) (64 位))。
2.之後找到IE Tab擴充套件外掛,但是好像僅僅是在chorme上嵌入了一個ie核心,這樣系統同樣是不相容的。
3.然後找到了油猴,想著寫一個指令碼獲取mac地址存入c盤的一個txt文字,然後系統去讀取這個txt檔案即可。奈何油猴寫了個alert(‘hello world‘)也沒生效,不知道是使用方法有誤,還是相關機制阻止了這種毫無意義的打擾。想到另一個問題,如果是後端去儲存檔案,其實是儲存到伺服器,而後端讀取檔案也需要客戶端進行上傳。而客戶端指令碼是沒有許可權去操作檔案直接儲存到txt(有點木馬的特性,奈何沒有涉及到這一塊的技術)。所以該方案暫時擱置。
4.又找到一個外掛PluginOK,但是是付費外掛,原理應該是js呼叫c++去獲取計算機物理資訊,然後通過雙向通訊整合到系統。感覺有些大材小用,也沒有這麼多預算,暫時擱置,也沒實測。這一痛點,成就了一家企業。
提取問題的根本矛盾點:mac地址僅支援ie獲取,系統不相容ie,那麼就只能分成兩步,1.ie獲取mac 2.使用chorme登入系統操作,這1和2之間需要產生一個聯絡來把1獲取的資料傳遞給2,想到上次微信掃碼登入的邏輯,使用了預登陸機制,聯想到暴雪的安全令機制,有了些許思路:
1.login頁面建立一條預登陸記錄存入資料庫,且獲取到id,在頁面上拼接成安全令登入url
2.使用者使用ie登入前一步拼接而成的url獲取到mac地址且根據入參id存入到對應的資料庫記錄中(使用者的ie需要先進行配置:1.ie瀏覽器internet選項-安全-受信任的站點將系統域名加入受信任的站點 2.還是剛才的地方 自定義級別 將ActiveX控制元件和外掛相關許可權開啟)
3.返回原來的login頁面輸入賬號密碼即可登入,正常邏輯登入之後,配合預登陸id(可以從資料庫獲取mac地址)可以進行mac地址白名單的匹配
曲線救國,安全令增加一些互動效果,各處增加操作詳細說明,倒也算勉強完成了需求