WorkBuddy Skills → MCP/API
OAuth 2.0 鉴权 + 设备指纹绑定完整方案

���盖 OAuth 2.0 授权码流程、设备指纹生成与绑定、安全存储、服务端校验、防 Token 复制攻击的全链路实施方案

目录
一、OAuth 2.0 鉴权流程(泳道图) 二、设备指纹绑定流程(架构图) 三、硬件信号采集 → Python 实现 四、设备指纹 + WorkBuddy 实例绑定 五、OS 原生安全存储 六、OAuth 客户端集成 七、MCP 服务端校验中间件 八、攻击场景与防护验证 九、方案对比与推荐组合
🔐 一、OAuth 2.0 鉴权流程(四泳道)
流程说明

四条泳道(用户 → WorkBuddy → Skills → MCP/API)自上而下描述完整的 OAuth 2.0 授权码鉴权流程。包含 Token 缺失走 OAuth 和 Token 有效跳过 OAuth 两条路径。

流程图
步骤详解
用户 WorkBuddy Skills MCP / API 步骤 01 用户发起任务 步骤 02 加载 Skill 解析意图 步骤 03 调用 MCP 工具 步骤 04 查询本地 Token 缺失 → 走 OAuth 步骤 05 浏览器 OAuth 授权 登录 & 同意授权 步骤 06 接收回调 code→Token 步骤 07 Token 持久化加密存储 步骤 08 鉴权网关校验 Bearer 步骤 09 执行业务逻辑 → 返回数据 Token缺失 Token有效 跳过 OAuth ← 结果逐层回传:MCP → Skills → WorkBuddy → 用户
  1. 用户发起任务 — 在 WorkBuddy 对话框中输入需要调用 MCP 服务的指令
  2. WorkBuddy 加载 Skill — 根据指令匹配并加载对应 Skill,解析用户意图
  3. Skills 调用 MCP 工具 — Skill 执行业务逻辑,发起 tools/call 请求
  4. 查询本地 Token ⚠️ 关键分叉点 — WorkBuddy 检查 ~/.workbuddy/oauth_tokens/ 是否有有效凭证
  5. 浏览器 OAuth 授权 — Token 缺失时打开浏览器,用户登录第三方服务并同意授权
  6. 接收回调 code → Token — WorkBuddy 接收 authorization_code,向 Token 端点换取 access_token + refresh_token
  7. Token 持久化加密存储 — 将 Token 存入本地安全区(DPAPI/Keychain/Secret Service)
  8. 鉴权网关校验 Bearer Token — MCP 服务端验证 JWT 签名、过期时间、设备绑定
  9. 执行业务逻辑 — 鉴权通过后执行 MCP 工具,返回结果逐层回传至用户
🖥️ 二、设备指纹绑定流程

设备指纹是防 Token 复制的核心机制。每次 OAuth 授权时,WorkBuddy 将本机硬件指纹写入 JWT 的 cnf (confirmation) 声明。后续 MCP 请求必须携带相同的指纹,否则服务端拒绝访问。

硬件层 — 设备指纹生成 绑定层 — OAuth + JWT 签发 校验层 — 每次 MCP 请求 采集硬件信号 MachineGuid — 系统安装唯一ID BIOS 序列号 — 固件烧录,不可导出 主板序列号 — 物理级唯一标识 CPU ID — 处理器唯一标识 MAC 地址 / 磁盘序列号 — 补充 Windows: MachineGuid + PowerShell macOS: IOPlatformUUID + ioreg SHA-256 哈希组合 → 64位设备指纹哈希值 组合后不可逆,不泄露原始信号 ✓ 本机实测结果 4/4 核心信号采集成功 XIAOMI TM2413 / Win11 / i5-13500H / 32GB WorkBuddy 实例 ID UUID v4 — 首次安装生成,永不变化 软件层第二因子:同型号设备也能区分 HMAC-SHA256 绑定密钥 key = 设备指纹 · msg = 实例ID 🔐 双因子绑定:硬件 + 软件 = 唯一密钥 OAuth 授权时携带绑定信息 device_fingerprint = sha256_hash wb_instance_id = uuid_v4_string binding_key = hmac_result + PKCE code_verifier(防授权劫持) 服务端签发 JWT(含绑定声明) "cnf": { "device_fp": "sha256_hash_value", "wb_instance": "uuid_v4_string", "binding_key": "hmac_result" } 每次 MCP 请求 Authorization: Bearer <jwt_token> X-Device-Fingerprint: <current_fp> X-Binding-Key: <current_bk> 服务端逐项校验比对 ① JWT.cnf.device_fp == 请求头 device_fp ? ② JWT.cnf.binding_key == 请求头 binding_key ? 匹配 ✓ 不匹配 ✗ 200 OK · 正常执行业务逻辑 身份确认 ✓ 设备合法 ✓ 绑定一致 ✓ 继续执行 MCP 工具调用 403 Forbidden · 立即吊销 Token 设备指纹不匹配 → 拒绝请求 → 吊销凭据 复制 Token 到其他机器无效 🔒 安全存储层 — 密钥永不明文落盘 Windows: DPAPI(绑定用户SID+机器密钥)| macOS: Keychain(Secure Enclave 硬件级保护)| Linux: Secret Service(用户登录密码加密) 密钥写入平台安全区
📡 三、硬件信号采集 — Python 实现

跨平台硬件信号采集器,Windows / macOS / Linux 三端适配。信号哈希后不可逆,不泄露原始硬件信息。

import hashlib, platform, subprocess, uuid, json, os class DeviceFingerprint: """跨平台设备指纹生成器""" @staticmethod def _windows_signals() -> dict: signals = {} # 1) MachineGuid — Windows 安装实例唯一ID,重装系统才变 import winreg with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Cryptography") as key: signals['machine_guid'] = winreg.QueryValueEx(key, "MachineGuid")[0] # 2) BIOS 序列号 — 烧录在固件中,物理级不可复制 r = subprocess.run( ["powershell", "-Command", "(Get-WmiObject Win32_BIOS).SerialNumber"], capture_output=True, text=True, timeout=5) signals['bios_serial'] = r.stdout.strip() # 3) 主板序列号 — 备用物理标识 r = subprocess.run( ["powershell", "-Command", "(Get-WmiObject Win32_BaseBoard).SerialNumber"], capture_output=True, text=True, timeout=5) signals['board_serial'] = r.stdout.strip() return signals @staticmethod def _macos_signals() -> dict: signals = {} r = subprocess.run(["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"], capture_output=True, text=True, timeout=5) for line in r.stdout.split('\n'): if "IOPlatformUUID" in line: signals['platform_uuid'] = line.split('"')[-2] if "IOPlatformSerialNumber" in line: signals['serial'] = line.split('"')[-2] return signals @staticmethod def _linux_signals() -> dict: signals = {} with open('/etc/machine-id') as f: signals['machine_id'] = f.read().strip() with open('/sys/class/dmi/id/board_serial') as f: signals['board_serial'] = f.read().strip() return signals @classmethod def generate(cls) -> str: system = platform.system().lower() signals = {'windows': cls._windows_signals, 'darwin': cls._macos_signals, 'linux': cls._linux_signals}[system]() raw = json.dumps(signals, sort_keys=True) return hashlib.sha256(raw.encode()).hexdigest()
本机实测信号值
信号值可靠性
MachineGuid164ecf8e-d075-4349-8fbe-b2d4dd928efe★★★★★
BIOS 序列号67422/25SE01488★★★★☆
主板序列号525M59SRMP000559P02BS★★★★☆
CPU IDBFEBFBFF000B06A2★★★★☆
设备指纹(SHA-256)0712a0d153af628a68b972c6f878285ab094bbf97e64634332d4aa6b65292acc衍生值

测试设备: XIAOMI TM2413 / i5-13500H / 32GB / Windows 11 — 4/4 核心信号全部可用

🔗 四、设备指纹 + WorkBuddy 实例绑定

单靠硬件指纹不够——两台同型号批次的电脑可能有相同硬件信号。引入 WorkBuddy 实例 ID(软件层第二因子),双因子组合确保唯一性。

class WorkBuddyBinding: """将设备指纹与 WorkBuddy 实例绑定 — 双因子防复制""" CONFIG_PATH = os.path.expanduser('~/.workbuddy/config.json') @classmethod def get_or_create_instance_id(cls) -> str: """获取或创建 WorkBuddy 实例 ID(首次安装生成,永不变化)""" config = {} if os.path.exists(cls.CONFIG_PATH): with open(cls.CONFIG_PATH) as f: config = json.load(f) if 'instance_id' not in config: config['instance_id'] = str(uuid.uuid4()) # 首次生成 os.makedirs(os.path.dirname(cls.CONFIG_PATH), exist_ok=True) with open(cls.CONFIG_PATH, 'w') as f: json.dump(config, f, indent=2) return config['instance_id'] @classmethod def generate_binding_key(cls) -> str: """ 双因子绑定密钥 = HMAC-SHA256(设备指纹, 实例ID) - 硬件不同 → 指纹不同 → 密钥不同 - 实例不同 → ID 不同 → 密钥不同 """ fingerprint = DeviceFingerprint.generate() instance_id = cls.get_or_create_instance_id() return hmac.new( fingerprint.encode(), instance_id.encode(), hashlib.sha256 ).hexdigest() @classmethod def get_binding_proof(cls) -> dict: """返回完整绑定证明,用于 OAuth 请求""" return { 'device_fingerprint': DeviceFingerprint.generate(), 'wb_instance_id': cls.get_or_create_instance_id(), 'binding_key': cls.generate_binding_key(), }
双因子原理:两台完全相同的硬件(如公司采购的同一批次笔记本),设备指纹可能相同。但每台机器上 WorkBuddy 安装时生成的实例 ID 不同 → HMAC 结果不同 → 绑定密钥不同 → 无法冒充。
🔒 五、OS 原生安全存储

绑定密钥不能明文存储在文件里(可被复制)。必须存入操作系统原生加密区:Windows DPAPI / macOS Keychain / Linux Secret Service。

class SecureStorage: """OS 原生安全存储 — 密钥绑定到当前用户+当前机器""" @staticmethod def store(key_name: str, value: str): system = platform.system().lower() if system == 'windows': # DPAPI — 绑定当前 Windows 用户 SID + 机器密钥 # 同一用户同一台机器才能解密,复制文件到其他机器无效 script = ( f'$val = ConvertTo-SecureString "{value}" ' f'-AsPlainText -Force; ' f'ConvertFrom-SecureString $val | ' f'Set-Content "$env:USERPROFILE\\.workbuddy\\.device_key"' ) subprocess.run(["powershell", "-Command", script], check=True, capture_output=True) elif system == 'darwin': # macOS Keychain — Apple Silicon 上使用 Secure Enclave subprocess.run(["security", "add-generic-password", "-a", os.getenv('USER', 'workbuddy'), "-s", key_name, "-w", value, "-U"], check=True) elif system == 'linux': # Secret Service — GNOME Keyring / KWallet try: subprocess.run(["secret-tool", "store", "--label", key_name, "service", "workbuddy", "account", key_name], input=value, text=True, check=True) except FileNotFoundError: # 降级:文件 + 0600 权限(安全性不如原生) path = os.path.expanduser('~/.workbuddy/.device_key') with open(path, 'w') as f: f.write(value) os.chmod(path, 0o600) @staticmethod def retrieve(key_name: str) -> str: system = platform.system().lower() if system == 'windows': # 解密:只有同一用户同一台机器才能成功 script = ( f'$enc = Get-Content "$env:USERPROFILE\\.workbuddy\\.device_key" ' f'| ConvertTo-SecureString; ' '[Runtime.InteropServices.Marshal]::PtrToStringAuto(' '[Runtime.InteropServices.Marshal]::SecureStringToBSTR($enc))' ) r = subprocess.run(["powershell", "-Command", script], capture_output=True, text=True) return r.stdout.strip() elif system == 'darwin': r = subprocess.run(["security", "find-generic-password", "-a", os.getenv('USER', 'workbuddy'), "-s", key_name, "-w"], capture_output=True, text=True) return r.stdout.strip() if r.returncode == 0 else None elif system == 'linux': try: r = subprocess.run(["secret-tool", "lookup", "service", "workbuddy", "account", key_name], capture_output=True, text=True) return r.stdout.strip() except Exception: path = os.path.expanduser('~/.workbuddy/.device_key') return open(path).read().strip() if os.path.exists(path) else None
平台机制复制到其他机器可用?原因
WindowsDPAPI✗ 不可用加密时绑定当前用户 SID + 机器密钥
macOSKeychain✗ 不可用密钥受登录密码保护,Apple Silicon 走 Secure Enclave
LinuxSecret Service✗ 不可用密钥环用用户登录密码加密
🔐 六、OAuth 客户端集成

OAuth 授权时在 Token 交换请求和每次 MCP 调用中注入设备绑定信息。

class OAuthClient: """ OAuth 2.0 授权码流程 + 设备绑定 在标准 PKCE 基础上增加 device_fingerprint + binding_key """ @staticmethod def build_authorization_url() -> str: """构建授权页面 URL(含 PKCE challenge)""" code_verifier = secrets.token_urlsafe(32) code_challenge = base64url(hashlib.sha256( code_verifier.encode() ).digest()) # 将 code_verifier 存入内存(不落盘),回调时使用 SecureStorage.store('oauth_code_verifier', code_verifier) params = { 'response_type': 'code', 'client_id': CLIENT_ID, 'redirect_uri': REDIRECT_URI, 'code_challenge': code_challenge, 'code_challenge_method': 'S256', 'scope': 'mcp:tools mcp:resources', } return AUTHORIZE_ENDPOINT + '?' + urllib.parse.urlencode(params) @staticmethod def exchange_code_for_token(auth_code: str) -> dict: """用 code 换 Token,携带设备绑定信息""" code_verifier = SecureStorage.retrieve('oauth_code_verifier') proof = WorkBuddyBinding.get_binding_proof() response = requests.post(TOKEN_ENDPOINT, data={ 'grant_type': 'authorization_code', 'code': auth_code, 'code_verifier': code_verifier, 'client_id': CLIENT_ID, 'redirect_uri': REDIRECT_URI, # ↓ 设备绑定信息 ↓ 'device_fingerprint': proof['device_fingerprint'], 'wb_instance_id': proof['wb_instance_id'], 'binding_key': proof['binding_key'], }) token_data = response.json() # 将 Token 存入安全区(不是明文文件!) SecureStorage.store('mcp_access_token', token_data['access_token']) SecureStorage.store('mcp_refresh_token', token_data['refresh_token']) return token_data @staticmethod def call_mcp_tool(tool_name: str, params: dict) -> dict: """每次 MCP 调用携带设备绑定头""" token = SecureStorage.retrieve('mcp_access_token') proof = WorkBuddyBinding.get_binding_proof() headers = { 'Authorization': f'Bearer {token}', 'X-Device-Fingerprint': proof['device_fingerprint'], 'X-Binding-Key': proof['binding_key'], 'Content-Type': 'application/json', } return requests.post( f'{MCP_BASE_URL}/tools/call', json={'name': tool_name, 'arguments': params}, headers=headers ).json()
PKCE (RFC 7636) 防护:code_verifier 仅存于发起授权的机器内存中。即使另一台机器拦截到 authorization_code,没有 code_verifier 也无法换取 Token。
🛡️ 七、MCP 服务端校验中间件

服务端在签发 JWT 时将设备绑定写入声明,每次 MCP 请求时校验三层:签名 → 过期 → 设备绑定。

# ── 授权服务器:签发绑定了设备的 JWT ── def issue_token(user_id: str, token_request: dict) -> str: payload = { 'iss': 'mcp-auth-server', 'sub': user_id, 'iat': int(time.time()), 'exp': int(time.time()) + 900, # 15 分钟短效 'cnf': { # confirmation claim 'device_fp': token_request['device_fingerprint'], 'wb_instance': token_request['wb_instance_id'], 'binding_key': token_request['binding_key'], } } return jwt.encode(payload, PRIVATE_KEY, algorithm='RS256') # ── MCP 网关:设备绑定校验中间件 ── from functools import wraps def verify_device_binding(handler): @wraps(handler) def wrapper(request, *args, **kwargs): token = extract_bearer_token(request.headers) payload = jwt.decode(token, PUBLIC_KEY, algorithms=['RS256']) cnf = payload.get('cnf', {}) token_fp = cnf.get('device_fp') token_bk = cnf.get('binding_key') request_fp = request.headers.get('X-Device-Fingerprint') request_bk = request.headers.get('X-Binding-Key') # 校验 1:设备指纹 if not token_fp or token_fp != request_fp: revoke_token(token) # 立即吊销可疑 Token log_alert(f"device_fp mismatch for user {payload['sub']}") return error(403, 'Device fingerprint mismatch — token revoked') # 校验 2:绑定密钥(硬件+软件双因子) if not token_bk or token_bk != request_bk: revoke_token(token) log_alert(f"binding_key mismatch for user {payload['sub']}") return error(403, 'Binding key mismatch — token revoked') return handler(request, *args, **kwargs) return wrapper # ── 使用示例:将中间件挂载到 MCP 路由 ── @app.route('/tools/call', methods=['POST']) @verify_device_binding # ← 设备绑定校验 def handle_tool_call(request): payload = request.json result = execute_mcp_tool(payload['name'], payload['arguments']) return {'status': 'ok', 'result': result}
关键设计:校验失败立即 吊销 Token 并告警 —— 不仅拒绝本次请求,还要让被复制的 Token 彻底失效,防止攻击者反复尝试。
🛑 八、攻击场景与防护验证
攻击者做了什么结果失败原因
只复制 oauth_tokens/ 目录403 拒绝请求头中的设备指纹 ≠ JWT 中绑定的指纹
复制 oauth_tokens/ + config.json403 拒绝硬件不同 → 指纹不同 → 绑定密钥不同
复制全部文件 + 伪造设备指纹403 拒绝不知道原始硬件信号值,无法生成相同哈希
同一台机器复制到另一个 WorkBuddy 安装403 拒绝实例 ID 不同 → 绑定密钥不同
同一台机器 + 同一 WorkBuddy 安装通过本质是同一用户在同一机器使用(非攻击场景)
DPAPI 加密文件复制到其他机器解密失败DPAPI 绑定当前用户 SID + 机器密钥,异机无法解密
拦截 authorization_code 后伪造请求换 Token 失败缺少 PKCE code_verifier(仅存于原始机器安全区)
唯一无法防护的场景:攻击者有物理访问权限,在同一台机器上登录同一 Windows 用户账户 → 此时 DPAPI 可解密、设备指纹相同 → 这是操作系统级别的问题,超出应用层防护范围。
📊 九、方案对比与推荐组合
方案防复制强度实现复杂度标准化推荐度
短效 Token + Refresh 轮转★★☆低OAuth 2.0 标准辅助
PKCE★★☆低RFC 7636标配
设备指纹绑定★★★中自定义扩展⭐ 核心
DPoP (RFC 9449)★★★★高RFC 9449进阶
mTLS (RFC 8705)★★★★高RFC 8705企业级
Introspection + 风控★★★中RFC 7662辅助
推荐组合(三层纵深防御):
PKCE(防授权码劫持)+ 设备指纹绑定(防 Token 跨机复制)+ 短效 Token + Refresh 轮转(缩小攻击窗口)= 覆盖全链路的纵深防护体系