Windows 桌面软件授权保护:电脑绑定方案详解

📅 2026-03-19 👤 Elaine 👁️ 7 次阅读 ⏱️ 10 分钟阅读 ❤️ 0

🔐 Windows 桌面软件授权保护:电脑绑定方案

    <p class="subtitle">MFC / Qt / Flutter 三种开发框架的实现详解 | 2026-03-19</p>

    <div class="info">
        <div class="info-title">📋 目录</div>
        <ul>
            <li><a href="#绑定方式" style="color:#60a5fa;">电脑绑定方式对比</a></li>
            <li><a href="#实现方案" style="color:#60a5fa;">最佳实现方案推荐</a></li>
            <li><a href="#mfc实现" style="color:#60a5fa;">MFC 实现代码</a></li>
            <li><a href="#qt实现" style="color:#60a5fa;">Qt 实现代码</a></li>
            <li><a href="#flutter实现" style="color:#60a5fa;">Flutter 实现代码</a></li>
            <li><a href="#总结" style="color:#60a5fa;">总结</a></li>
        </ul>
    </div>

    <h2 id="绑定方式">🔍 电脑绑定方式对比</h2>

    <table>
        <tr>
            <th>绑定方式</th>
            <th>获取难度</th>
            <th>稳定性</th>
            <th>安全性</th>
            <th>说明</th>
        </tr>
        <tr>
            <td>MAC 地址</td>
            <td>⭐ 简单</td>
            <td>⭐⭐⭐⭐</td>
            <td>⭐⭐ 低</td>
            <td>可被伪造,但最常用</td>
        </tr>
        <tr>
            <td>CPU 序列号</td>
            <td>⭐ 简单</td>
            <td>⭐⭐⭐⭐</td>
            <td>⭐⭐⭐ 中</td>
            <td>大部分 CPU 可获取,较稳定</td>
        </tr>
        <tr>
            <td>硬盘序列号</td>
            <td>⭐ 简单</td>
            <td>⭐⭐⭐</td>
            <td>⭐⭐⭐ 中</td>
            <td>可能因重装系统改变</td>
        </tr>
        <tr>
            <td>主板序列号</td>
            <td>⭐⭐ 中等</td>
            <td>⭐⭐⭐⭐⭐</td>
            <td>⭐⭐⭐⭐ 高</td>
            <td>更换主板会失效,最安全</td>
        </tr>
        <tr>
            <td>Windows SID</td>
            <td>⭐ 简单</td>
            <td>⭐⭐⭐⭐</td>
            <td>⭐⭐⭐ 中</td>
            <td>重装系统会改变</td>
        </tr>
        <tr>
            <td>主板 BIOS UUID</td>
            <td>⭐⭐ 中等</td>
            <td>⭐⭐⭐⭐⭐</td>
            <td>⭐⭐⭐⭐⭐ 最高</td>
            <td>最可靠的硬件标识</td>
        </tr>
    </table>

    <div class="warning">
        <div class="warning-title">⚠️ MAC 地址可被伪造</div>
        <p>虽然 MAC 地址最简单,但有工具可以修改(Windows 注册表或网卡驱动)。如果安全性要求高,不建议单独使用 MAC 地址。</p>
    </div>

    <h2 id="实现方案">💡 最佳实现方案推荐</h2>

    <div class="section">
        <h3>方案一:单硬件标识(简单场景)</h3>
        <p>直接使用主板 UUID 或 BIOS UUID,够简单够用。</p>
        <pre><code>机器码 = 主板UUID

验证方式 = 比对当前机器码 vs 注册时机器码

    <div class="section">
        <h3>方案二:多硬件标识组合(高安全场景)</h3>
        <p>组合多个硬件标识,即使一个被伪造也不容易被绕过。</p>
        <pre><code>机器码 = MD5(主板UUID + CPU序列号 + 硬盘序列号)

验证方式 = 比对组合后的哈希值

    <div class="section">
        <h3>方案三:服务器验证(最安全)</h3>
        <p>把机器码发送到授权服务器验证,支持远程禁用授权。</p>
        <pre><code>1. 软件首次运行生成机器码,上传到服务器
  1. 服务器返回授权码(基于机器码加密)
  2. 每次启动验证授权码有效性
  3. 服务器可随时禁用任何机器的授权
    <h2 id="mfc实现">💻 MFC 实现代码</h2>
    
    <div class="section">
        <h3>核心实现(MFC/C++)</h3>
        <pre><code>// MachineBinding.h
    

    pragma once

    include

    include

class CMachineBinding
{
public:
// 获取各种硬件标识
static std::wstring GetMACAddress(); // MAC 地址
static std::wstring GetCPUId(); // CPU ID
static std::wstring GetHardDiskSerial(); // 硬盘序列号
static std::wstring GetMotherboardUUID(); // 主板 UUID
static std::wstring GetMachineCode(); // 组合机器码
static std::wstring MD5Hash(const std::wstring& input);
static bool CheckAuthorization(const std::wstring& storedCode);
};

    <div class="section">
        <h3>MachineBinding.cpp 实现</h3>
        <pre><code>// MachineBinding.cpp

include "MachineBinding.h"

include

include

include

include

include

include

include

pragma comment(lib, "wbemuuid.lib")

pragma comment(lib, "openssl/libcrypto.lib")

// 获取 MAC 地址(通过 WMI)
std::wstring CMachineBinding::GetMACAddress() {
std::wstring mac;
CoInitializeEx(0, COINIT_MULTITHREADED);
CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, NULL, NULL);

IWbemLocator* pLoc = nullptr;
IWbemServices* pSvc = nullptr;

if (CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
    IID_IWbemLocator, (LPVOID*)&pLoc) == S_OK) {

    if (pLoc->ConnectServer(BSTR(L"ROOT\\CIMV2"), NULL, NULL, NULL,
        WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pSvc) == S_OK) {

        CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
            NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, NULL);

        IEnumWbemClassObject* pEnumerator = NULL;
        pSvc->ExecQuery(BSTR(L"WQL"), 
            BSTR(L"SELECT MACAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled=TRUE"),
            WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator);

        if (pEnumerator) {
            IWbemClassObject* pClassObj = NULL;
            ULONG uReturn = 0;
            while (pEnumerator->Next(WBEM_INFINITE, 1, &pClassObj, &uReturn) == S_OK) {
                VARIANT vtProp;
                pClassObj->Get(L"MACAddress", 0, &vtProp, 0, 0);
                if (vtProp.vt == VT_BSTR && vtProp.bstrVal) {
                    mac = vtProp.bstrVal;
                    mac.erase(std::remove(mac.begin(), mac.end(), L':'), mac.end());
                }
                VariantClear(&vtProp);
                pClassObj->Release();
                if (!mac.empty()) break;
            }
            pEnumerator->Release();
        }
    }
}
if (pSvc) pSvc->Release();
if (pLoc) pLoc->Release();
CoUninitialize();
return mac;

}

// 获取 CPU ID
std::wstring CMachineBinding::GetCPUId() {
INT CPUSerial[4];
__asm {
mov eax, 1
cpuid
mov CPUSerial[0], eax
mov CPUSerial[1], ebx
mov CPUSerial[2], ecx
mov CPUSerial[3], edx
}

std::wstringstream ss;
ss << std::hex << std::uppercase;
ss << std::setfill(L'0') << std::setw(8) << CPUSerial[0];
ss << std::setfill(L'0') << std::setw(8) << CPUSerial[1];
ss << std::setfill(L'0') << std::setw(8) << CPUSerial[2];
ss << std::setfill(L'0') << std::setw(8) << CPUSerial[3];
return ss.str();

}

// 获取主板 UUID(最可靠)
std::wstring CMachineBinding::GetMotherboardUUID() {
std::wstring uuid;
CoInitializeEx(0, COINIT_MULTITHREADED);
CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, NULL, NULL);

IWbemLocator* pLoc = nullptr;
IWbemServices* pSvc = nullptr;

if (CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
    IID_IWbemLocator, (LPVOID*)&pLoc) == S_OK) {

    if (pLoc->ConnectServer(BSTR(L"ROOT\\CIMV2"), NULL, NULL, NULL,
        WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pSvc) == S_OK) {

        CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
            NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, NULL);

        IEnumWbemClassObject* pEnumerator = NULL;
        pSvc->ExecQuery(BSTR(L"WQL"), 
            BSTR(L"SELECT UUID FROM Win32_ComputerSystemProduct"),
            WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator);

        if (pEnumerator) {
            IWbemClassObject* pClassObj = NULL;
            ULONG uReturn = 0;
            while (pEnumerator->Next(WBEM_INFINITE, 1, &pClassObj, &uReturn) == S_OK) {
                VARIANT vtProp;
                pClassObj->Get(L"UUID", 0, &vtProp, 0, 0);
                if (vtProp.vt == VT_BSTR && vtProp.bstrVal) {
                    uuid = vtProp.bstrVal;
                    uuid.erase(std::remove(uuid.begin(), uuid.end(), L'-'), uuid.end());
                }
                VariantClear(&vtProp);
                pClassObj->Release();
                if (!uuid.empty() && 
                    uuid != L"FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") {
                    break;
                }
            }
            pEnumerator->Release();
        }
    }
}
if (pSvc) pSvc->Release();
if (pLoc) pLoc->Release();
CoUninitialize();
return uuid;

}

// MD5 哈希
std::wstring CMachineBinding::MD5Hash(const std::wstring& input) {
unsigned char digest[MD5_DIGEST_LENGTH];
std::string inputStr(input.begin(), input.end());
MD5((const unsigned char*)inputStr.c_str(), inputStr.length(), digest);

std::stringstream ss;
for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
    ss << std::hex << std::setw(2) << std::setfill('0') << (int)digest[i];
}
return std::wstring(ss.str().begin(), ss.str().end());

}

// 获取组合机器码
std::wstring CMachineBinding::GetMachineCode() {
std::wstring uuid = GetMotherboardUUID();
std::wstring cpu = GetCPUId();
std::wstring mac = GetMACAddress();

// 组合多个标识
std::wstring combined = uuid + cpu + mac;
return MD5Hash(combined);

}

// 检测授权
bool CMachineBinding::CheckAuthorization(const std::wstring& storedCode) {
return GetMachineCode() == storedCode;
}

    <div class="section">
        <h3>MFC 使用示例</h3>
        <pre><code>// 在软件启动时调用

void CMyApp::Init()
{
std::wstring machineCode = CMachineBinding::GetMachineCode();

// 读取已存储的注册码
CString storedCode;
AfxGetApp()->GetProfileString(L"License", L"Code", L"", storedCode.GetBuffer(64), 64);
storedCode.ReleaseBuffer();

if (storedCode.IsEmpty()) {
    // 首次运行,显示机器码让用户注册
    MessageBox(NULL, (L"您的机器码:\n" + machineCode).c_str(), 
        L"软件注册", MB_OK);
} else {
    // 验证授权
    if (!CMachineBinding::CheckAuthorization(std::wstring(storedCode))) {
        MessageBox(NULL, L"授权验证失败!\n软件只能在注册电脑上使用。", 
            L"错误", MB_ICONERROR);
        exit(1);
    }
}

}

    <h2 id="qt实现">💻 Qt 实现代码</h2>

    <div class="section">
        <h3>MachineInfo.h</h3>
        <pre><code>#ifndef MACHINEINFO_H

define MACHINEINFO_H

include

class MachineInfo
{
public:
static QString getMACAddress();
static QString getCPUId();
static QString getMotherboardUUID();
static QString getMachineCode();
static QString md5Hash(const QString &input);
static bool checkAuthorization(const QString &storedCode);
};

endif // MACHINEINFO_H

    </div>

    <div class="section">
        <h3>MachineInfo.cpp 实现</h3>
        <pre><code>#include "MachineInfo.h"

include

include

include

include

include

pragma comment(lib, "wbemuuid.lib")

// 获取 MAC 地址
QString MachineInfo::getMACAddress() {
QString mac;
CoInitializeEx(NULL, COINIT_MULTITHREADED);
CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, NULL, NULL);

IWbemLocator *pLoc = NULL;
if (CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
    IID_IWbemLocator, (LPVOID*)&pLoc) == S_OK) {

    IWbemServices *pSvc = NULL;
    if (pLoc->ConnectServer(BSTR(L"ROOT\\CIMV2"), NULL, NULL, NULL,
        WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pSvc) == S_OK) {

        CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
            NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, NULL);

        IEnumWbemClassObject *pEnumerator = NULL;
        pSvc->ExecQuery(BSTR(L"WQL"), 
            BSTR(L"SELECT MACAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled=TRUE"),
            WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator);

        if (pEnumerator) {
            IWbemClassObject *pClassObj = NULL;
            ULONG uReturn = 0;
            while (pEnumerator->Next(WBEM_INFINITE, 1, &pClassObj, &uReturn) == S_OK) {
                VARIANT vtProp;
                pClassObj->Get(L"MACAddress", 0, &vtProp, 0, 0);
                if (vtProp.vt == VT_BSTR && vtProp.bstrVal) {
                    mac = QString::fromWCharArray(vtProp.bstrVal);
                    mac.remove(':');
                }
                VariantClear(&vtProp);
                pClassObj->Release();
                if (!mac.isEmpty()) break;
            }
            pEnumerator->Release();
        }
    }
    if (pSvc) pSvc->Release();
}
if (pLoc) pLoc->Release();
CoUninitialize();
return mac;

}

// 获取 CPU ID
QString MachineInfo::getCPUId() {
INT CPUSerial[4];
__asm {
mov eax, 1
cpuid
mov CPUSerial[0], eax
mov CPUSerial[1], ebx
mov CPUSerial[2], ecx
mov CPUSerial[3], edx
}

return QString("%1%2%3%4")
    .arg(CPUSerial[0], 8, 16, QChar('0'))
    .arg(CPUSerial[1], 8, 16, QChar('0'))
    .arg(CPUSerial[2], 8, 16, QChar('0'))
    .arg(CPUSerial[3], 8, 16, QChar('0'))
    .toUpper();

}

// 获取主板 UUID
QString MachineInfo::getMotherboardUUID() {
QString uuid;
CoInitializeEx(NULL, COINIT_MULTITHREADED);
CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, NULL, NULL);

IWbemLocator *pLoc = NULL;
if (CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
    IID_IWbemLocator, (LPVOID*)&pLoc) == S_OK) {

    IWbemServices *pSvc = NULL;
    if (pLoc->ConnectServer(BSTR(L"ROOT\\CIMV2"), NULL, NULL, NULL,
        WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pSvc) == S_OK) {

        CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
            NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, NULL);

        IEnumWbemClassObject *pEnumerator = NULL;
        pSvc->ExecQuery(BSTR(L"WQL"), 
            BSTR(L"SELECT UUID FROM Win32_ComputerSystemProduct"),
            WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator);

        if (pEnumerator) {
            IWbemClassObject *pClassObj = NULL;
            ULONG uReturn = 0;
            while (pEnumerator->Next(WBEM_INFINITE, 1, &pClassObj, &uReturn) == S_OK) {
                VARIANT vtProp;
                pClassObj->Get(L"UUID", 0, &vtProp, 0, 0);
                if (vtProp.vt == VT_BSTR && vtProp.bstrVal) {
                    uuid = QString::fromWCharArray(vtProp.bstrVal);
                    uuid.remove('-');
                }
                VariantClear(&vtProp);
                pClassObj->Release();
                if (!uuid.isEmpty() && 
                    uuid != "FFFFFFFFFFFF") break;
            }
            pEnumerator->Release();
        }
    }
    if (pSvc) pSvc->Release();
}
if (pLoc) pLoc->Release();
CoUninitialize();
return uuid;

}

// MD5 哈希
QString MachineInfo::md5Hash(const QString &input) {
QByteArray hash = QCryptographicHash::hash(
input.toUtf8(), QCryptographicHash::Md5);
return hash.toHex().toUpper();
}

// 获取组合机器码
QString MachineInfo::getMachineCode() {
QString uuid = getMotherboardUUID();
QString cpu = getCPUId();
QString mac = getMACAddress();
return md5Hash(uuid + cpu + mac);
}

// 检测授权
bool MachineInfo::checkAuthorization(const QString &storedCode) {
return getMachineCode() == storedCode;
}

    <div class="section">
        <h3>Qt 使用示例</h3>
        <pre><code>// main.cpp

include

include

include

include "MachineInfo.h"

int main(int argc, char *argv[])
{
QApplication a(argc, argv);

// 读取注册码
QSettings settings("YourCompany", "YourApp");
QString registeredCode = settings.value("LicenseCode").toString();

if (registeredCode.isEmpty()) {
    QString machineCode = MachineInfo::getMachineCode();
    QMessageBox::information(nullptr, "注册", 
        QString("您的机器码:\n%1\n\n请将此码发送给供应商获取授权码。").arg(machineCode));
    return 0;
}

if (!MachineInfo::checkAuthorization(registeredCode)) {
    QMessageBox::critical(nullptr, "错误", 
        "授权验证失败!\n软件只能在注册电脑上使用。");
    return 1;
}

return a.exec();

}

    <h2 id="flutter实现">💻 Flutter 实现代码</h2>

    <div class="section">
        <h3>pubspec.yaml 添加依赖</h3>
        <pre><code>dependencies:

flutter:
sdk: flutter
crypto: ^3.0.3
path_provider: ^2.1.1

    <div class="section">
        <h3>Dart 实现代码</h3>
        <pre><code>import 'dart:io';

import 'package:crypto/crypto.dart';
import 'dart:convert';

class MachineBinding {
/// 获取 MAC 地址
static Future getMACAddress() async {
try {
final result = await Process.run('getmac', []);
String output = result.stdout.toString();

  // 解析 MAC 地址
  RegExp reg = RegExp(r'([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}');
  Match? match = reg.firstMatch(output);

  if (match != null) {
    return match.group(0)!.replaceAll('-', '').toUpperCase();
  }
  return '';
} catch (e) {
  return '';
}

}

/// 获取 CPU ID
static Future getCPUId() async {
try {
final result = await Process.run('wmic', ['cpu', 'get', 'ProcessorId']);
String output = result.stdout.toString().trim();
List lines = output.split('\n');
if (lines.length >= 2) {
return lines[1].trim().toUpperCase();
}
return '';
} catch (e) {
return '';
}
}

/// 获取主板 UUID
static Future getMotherboardUUID() async {
try {
final result = await Process.run('wmic',
['computersystemproduct', 'get', 'uuid']);
String output = result.stdout.toString().trim();
List lines = output.split('\n');
if (lines.length >= 2) {
String uuid = lines[1].trim().toUpperCase();
uuid = uuid.replaceAll('-', '');
if (uuid.contains('FFFFFFFF')) return '';
return uuid;
}
return '';
} catch (e) {
return '';
}
}

/// MD5 哈希
static String md5Hash(String input) {
var bytes = utf8.encode(input);
var digest = md5.convert(bytes);
return digest.toString().toUpperCase();
}

/// 获取组合机器码
static Future getMachineCode() async {
final uuid = await getMotherboardUUID();
final cpu = await getCPUId();
final mac = await getMACAddress();
return md5Hash(uuid + cpu + mac);
}

/// 检测授权
static Future checkAuthorization(String storedCode) async {
return await getMachineCode() == storedCode;
}
}

    <div class="section">
        <h3>Flutter 使用示例</h3>
        <pre><code>import 'package:flutter/material.dart';

import 'machine_binding.dart';

class LicenseCheck extends StatefulWidget {
@override
_LicenseCheckState createState() => _LicenseCheckState();
}

class _LicenseCheckState extends State {
bool _isLoading = true;
String? _machineCode;

@override
void initState() {
super.initState();
_checkLicense();
}

Future _checkLicense() async {
String code = await MachineBinding.getMachineCode();

setState(() {
  _machineCode = code;
  _isLoading = false;
});

// TODO: 从本地存储读取已注册的码进行验证
// String? storedCode = await getStoredLicenseCode();
// if (storedCode == null) {
//   _showRegistrationDialog(code);
// } else if (!await MachineBinding.checkAuthorization(storedCode)) {
//   _showAuthFailedDialog();
// }

}

void _showRegistrationDialog(String machineCode) {
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
title: Text('软件注册'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('您的机器码:'),
SelectableText(
machineCode,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
SizedBox(height: 16),
Text('请将此码发送给供应商获取授权码。'),
],
),
actions: [
TextButton(
onPressed: () => exit(0),
child: Text('退出'),
),
],
),
);
}

@override
Widget build(BuildContext context) {
if (_isLoading) {
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}

return Scaffold(
  appBar: AppBar(title: Text('软件注册')),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('您的机器码:'),
        SelectableText(
          _machineCode ?? '获取失败',
          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
        ),
      ],
    ),
  ),
);

}
}

    <h2 id="总结">📝 总结</h2>

    <div class="success">
        <div class="success-title">✅ 推荐方案</div>
        <ul>
            <li><strong>简单场景</strong>:直接使用主板 UUID</li>
            <li><strong>中等安全</strong>:使用组合机器码 MD5(uuid + cpu + mac)</li>
            <li><strong>高安全</strong>:服务器验证 + 本地多重绑定</li>
        </ul>
    </div>

    <div class="warning">
        <div class="warning-title">⚠️ 注意事项</div>
        <ul>
            <li>机器码存储要加密,防止被直接修改</li>
            <li>可以配合授权码加密,增强安全性</li>
            <li>换电脑/换主板会导致授权失效</li>
            <li>MAC 地址可伪造,单独使用不安全</li>
            <li>建议同时提供管理员解锁机制</li>
        </ul>
    </div>
最后更新:2026-08-11 06:47