Python中的WebAuthn/FIDO协议实现:无密码身份验证的底层细节

Python中的WebAuthn/FIDO协议实现:无密码身份验证的底层细节

大家好,今天我们来深入探讨WebAuthn/FIDO协议在Python中的实现,并着重关注无密码身份验证的底层细节。WebAuthn/FIDO是目前最先进、最安全的Web身份验证标准之一,它允许用户使用各种硬件认证器(如指纹识别器、USB安全密钥等)进行身份验证,从而摆脱对传统密码的依赖。

1. WebAuthn/FIDO协议概述

WebAuthn(Web Authentication)是由W3C发布的Web API规范,它定义了Web应用程序如何与认证器进行交互。FIDO(Fast Identity Online)联盟则定义了具体的认证协议,例如CTAP(Client to Authenticator Protocol),用于客户端和认证器之间的通信。

WebAuthn/FIDO协议的核心在于使用公钥密码学进行身份验证。用户设备上的认证器会生成一对密钥:私钥保存在认证器内部,用于签名;公钥则注册到服务器。当用户需要进行身份验证时,服务器会生成一个挑战(challenge),发送给客户端。客户端将这个挑战传递给认证器,认证器使用私钥对挑战进行签名,并将签名返回给服务器。服务器使用之前注册的公钥验证签名,如果验证通过,则认为用户身份验证成功。

2. WebAuthn/FIDO协议的关键流程

WebAuthn/FIDO协议主要包含两个关键流程:注册(Registration)和认证(Authentication)。

  • 注册 (Registration): 用户首次使用WebAuthn进行身份验证时,需要先注册一个新的认证器。这个过程涉及到生成密钥对,并将公钥注册到服务器。
  • 认证 (Authentication): 用户进行身份验证时,客户端向认证器请求签名,服务器验证签名以确认用户身份。

3. Python中的WebAuthn库:fido2

在Python中,fido2库是实现WebAuthn/FIDO协议的首选工具。它提供了对CTAP1/CTAP2协议的完整支持,并允许开发者方便地集成WebAuthn到他们的Web应用程序中。

安装 fido2 库:

pip install fido2

4. 注册流程的Python实现

下面我们通过一个简单的例子来演示如何在Python中实现WebAuthn的注册流程。

from fido2.server import Fido2Server, RelyingParty
from fido2.client import ClientData, AttestationObject
from fido2 import cbor
import base64
import os
from typing import Dict, Any

# 服务器端配置
RP_ID = "example.com"  # Relying Party ID, 你的域名
RP_NAME = "Example WebAuthn Site"  # Relying Party Name, 你的网站名称
ORIGIN = "https://example.com"  # 网站的Origin,必须是HTTPS

# 初始化Fido2Server
rp = RelyingParty(RP_ID, RP_NAME)
server = Fido2Server(rp)

# 模拟用户数据库,存储用户的凭据信息
user_credentials: Dict[str, Dict[str, Any]] = {}

def start_registration(user_id: str, user_name: str) -> Dict[str, Any]:
    """
    开始注册流程,生成注册选项。
    """
    attestation_options = server.register_begin(
        {"id": user_id.encode("utf-8"), "name": user_name},
        attestation="direct",  # 设置为 "direct" 以获取认证语句
        user_verification="preferred" # 设置用户验证方式
    )
    return attestation_options

def finish_registration(user_id: str, registration_response: Dict[str, Any]) -> bool:
    """
    完成注册流程,验证认证结果,并将凭据信息存储到数据库。
    """
    try:
        client_data = ClientData(registration_response["clientDataJSON"])
        attestation_object = AttestationObject(registration_response["attestationObject"])

        server.register_complete(
            client_data,
            attestation_object,
            [user_credentials[user_id]["credential_id"] for user_id in user_credentials if "credential_id" in user_credentials[user_id]]
        )

        credential_id = base64.b64encode(attestation_object.credential_data.credential_id).decode("utf-8")

        # 将凭据信息存储到数据库
        user_credentials[user_id] = {
            "credential_id": credential_id,
            "public_key": base64.b64encode(attestation_object.credential_data.public_key).decode("utf-8"),
            "sign_count": 0  # 初始化签名计数器
        }

        return True
    except Exception as e:
        print(f"注册失败: {e}")
        return False

# 模拟客户端交互
user_id = "user123"
user_name = "John Doe"

# 1. 客户端请求注册选项
registration_options = start_registration(user_id, user_name)
print("注册选项:", registration_options)

# 模拟客户端操作:将注册选项发送给认证器,认证器生成密钥对并返回认证结果
#  (这里需要模拟客户端与认证器的交互,例如使用JavaScript的WebAuthn API。这里只演示服务端代码)
#  假设客户端返回的认证结果如下:
registration_response = {
    "clientDataJSON": base64.b64encode(b'{"type": "webauthn.create", "challenge": "xxx", "origin": "https://example.com"}').decode("utf-8"), # 替换为真实的clientDataJSON
    "attestationObject": base64.b64encode(b'xxx').decode("utf-8") # 替换为真实的attestationObject
}

# 2. 客户端将认证结果发送给服务器
registration_result = finish_registration(user_id, registration_response)

if registration_result:
    print("注册成功!")
    print("用户凭据:", user_credentials[user_id])
else:
    print("注册失败!")

代码解释:

  • Fido2Server:用于处理WebAuthn协议的核心类。它负责生成注册和认证选项,并验证认证结果。
  • RelyingParty:表示Web应用程序本身。需要提供Relying Party ID(通常是域名)和Relying Party Name。
  • start_registration:生成注册选项。这些选项包含了服务器的配置信息,以及一些安全参数。
  • finish_registration:验证认证结果。它会解析客户端返回的clientDataJSONattestationObject,并使用server.register_complete方法进行验证。验证成功后,将凭据信息存储到数据库。
  • user_credentials:模拟用户数据库。实际应用中,你需要使用真正的数据库来存储用户的凭据信息。
  • registration_response:模拟客户端返回的认证结果。实际应用中,你需要使用JavaScript的WebAuthn API从客户端获取这个结果。

5. 认证流程的Python实现

下面我们通过一个简单的例子来演示如何在Python中实现WebAuthn的认证流程。


from fido2.server import Fido2Server, RelyingParty
from fido2.client import ClientData, AttestationObject, AuthenticatorData
from fido2 import cbor
import base64
import os
from typing import Dict, Any

# 服务器端配置 (与注册流程相同)
RP_ID = "example.com"
RP_NAME = "Example WebAuthn Site"
ORIGIN = "https://example.com"

# 初始化Fido2Server
rp = RelyingParty(RP_ID, RP_NAME)
server = Fido2Server(rp)

# 模拟用户数据库 (与注册流程相同)
user_credentials: Dict[str, Dict[str, Any]] = {
    "user123": {
        "credential_id": "eyJoZWFkZXIiOnsiYWxnIjoiRVMyNTYiLCJ0eXAiOiJqd2sifSwiYWxnIjoiRVMyNTYiLCJ0eXAiOiJqd2siLCJ4IjoiWV93ajJ3aVlwUlVVZ09VZU9wT192eV9rR19lVzB5eU94Qnl2VnJ0Z01uZyIsInkiOiJ6dG5QY0x1b014c1FjV3d3NzN5d21mRkJxRzV0T1l2R0c5c19sN3J3Z28iLCJrdHkiOiJFQyIsImNydiI6IlAtMjU2In0=", # 替换为实际注册时存储的 credential_id
        "public_key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWd32J27Xv6gT3lWl27J22J4Y3w9dC+o57x4R7d3+0Y7d7/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ3/eJ

更多IT精英技术系列讲座,到智猿学院

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注