Web身份验证(WebAuthn)

news2024/11/26 2:47:30

目录

1、WebAuthn简介

2、FIDO2:客户端到验证器协议(CTAP)

3、浏览器和平台

4、Web身份验证API 基本流程

5、使用 WebAuthn API

5.1 注册WebAuthn凭据

5.2使用WebAuthn凭据进行身份验证

6、 WebAuthn演示(注册和认证)

6.1 注册演示:

6.2 认证演示:

6.3 浏览器中存储


1、WebAuthn简介

WebAuthn 是FIDO联盟FIDO2规范集的核心组件,是一种基于Web的API,允许网站更新其登录页面,以便在支持的浏览器和平台上添加基于FIDO的身份验证。FIDO2使用户能够利用常见设备轻松地在移动的和桌面环境中对在线服务进行身份验证。

Web服务和应用程序可以-并且应该-打开此功能,通过生物识别技术,移动的设备和/或FIDO安全密钥为用户提供更轻松的登录体验-并且比单独的密码具有更高的安全性。

FIDO联盟决定与万维网国际标准组织万维网联盟(W3C)合作,为整个Web平台标准化FIDO身份验证。这种标准化将通过支持该标准的Web浏览器和Web应用服务器的整个社区来发展FIDO生态系统。

它允许服务器与现在内置于设备中的强身份验证器集成,如Windows Hello或Apple的Touch ID。为网站创建私钥-公钥对(称为 凭据 )而不是密码。私钥安全地存储在用户的设备上;公钥和随机生成的凭据 ID 将发送到服务器进行存储。然后,服务器可以使用该公钥来证明用户的身份。

公钥不是秘密的,因为如果没有相应的私钥,它实际上是无用的。服务器未收到任何机密这一事实对用户和组织的安全性具有深远的影响。数据库对黑客不再那么有吸引力,因为公钥对他们没有用。

FIDO联盟成员公司于2015年将FIDO规范提交给W3C进行正式标准化。然后,他们在W3C内部工作,最终确定了API,这被称为Web身份验证或WebAuthn。WebAuthn于2019年3月被正式认可为W3C Web标准。如今WebAuthn是FIDO联盟FIDO2规范的一部分,FIDO联盟运行认证程序以确保合规性。

什么是公钥密码?

公钥密码学发明于20世纪70年代,是解决共享秘密问题的一种方法。它是现代互联网安全的支柱;例如,每次我们连接到HTTPS网站时,都会发生公钥交易。

公钥加密使用密钥对的概念;一个私钥由用户安全存储,一个公钥可以与服务器共享。这些“密钥”是彼此具有数学关系的长随机数。

2、FIDO2:客户端到验证器协议(CTAP)

FIDO2的另一个组件,客户端到认证者协议(CTAP)
,是WebAuthn的补充。它使外部身份验证器(如安全密钥或移动的电话)能够与支持WebAuthn的浏览器一起使用,并且还可以用作桌面应用程序和Web服务的身份验证器。

3、浏览器和平台

WebAuthn目前在Google Chrome
、Mozilla  Firefox、Microsoft Edge和Apple Safari
 Web浏览器以及Windows 10和Android
平台中得到支持。

浏览器兼容性如下:

4、Web身份验证API 基本流程

本节规范性地指定了用于创建和使用公钥凭据的API。基本的 这个想法是凭证属于用户并由WebAuthn Authenticator管理,WebAuthn依赖方通过客户端平台与WebAuthn Authenticator交互。 依赖方脚本可以(在用户同意的情况下)请求 浏览器以创建新凭证供信赖方将来使用。参见下图:

注册流程:

1、依赖方服务端开始准备用户信息,以及依赖方信息,这些属于公钥凭证创建相关内容选项。 

2、把对应信息在依赖房应用程序通过WebAuthnAPI,生成依赖方id,用户信息,依赖方信息,客户数据哈希,把这些信息发送身份验证器进行验证。

3、身份验证器开始验证用户信息,会生成新的密钥对,和对应认证。

4、身份验证器,把新的公钥和认证id等相关认证信息返回。

5、经浏览器和依赖方js应用返回客户JOSN数据,认证对象信息,这一步是验证器认证响应。

6、服务端进行验证。

脚本还可以请求用户的权限,以便使用现有凭据执行身份验证操作。参见下图1、依赖方服务端发起询问,需要公钥认证请求需要的参数。


 身份验证流程:

1、依赖方服务端发起询问,需要公钥认证请求需要的参数。

2、经依赖方JS应用,浏览器得到依赖方id,客户对应的哈希数据发送给身份验证器。

3、身份验证器开始验证用户信息,开始创建认证信息。

4、验证数据签名返回给浏览器。

5、经依赖方返回客户端的json数据,验证数据,签名等信息。

6、依赖方服务端

5、使用 WebAuthn API

5.1 注册WebAuthn凭据

在基于密码的用户注册流程中,服务器通常会向用户呈现一个表单,询问用户名和密码。密码将被发送到服务器进行存储。

在WebAuthn中,服务器必须提供将用户绑定到凭证(私钥-公钥对)的数据;该数据包括用户和组织(也称为“依赖方”)的标识符。然后,网站将使用Web身份验证API来提示用户创建新的密钥对。需要注意的是,我们需要从服务器随机生成的字符串作为挑战,以防止重放攻击。

 

navigator.credentials.create()

服务器将通过调用客户机上的navigator.credentials.create()开始创建新的凭据。

 
navigator.credentials.create({ publicKeyCredentialCreationOptions })
  .then(function (newCredentialInfo) {
    // 发送新的凭证信息到服务器进行验证和注册.
  }).catch(function (err) {
    // 没有可接受的身份验证者或用户拒绝同意。再做对应处理。
  });
publicKeyCredentialCreationOptions 对象包含许多必需和可选字段,服务器指定这些字段为用户创建新凭证。
const publicKeyCredentialCreationOptions  = {
  // 在服务器上生成的加密随机字节的缓冲区,并且需要防止“重放攻击”。
  challenge: bufferToBase64URLString(utf8StringToBuffer('sd')),
  // 服务器希望从认证器接收证明数据(direct)
  attestation: 'direct',
  // 这是一个对象数组,描述服务器可以接受哪些公钥类型
  // -7表示服务器接受使用SHA-256签名算法的椭圆曲线公钥
  pubKeyCredParams: [
    {
      alg: -7,
      type: 'public-key',
    },
  ],
  // 依赖方的id,是浏览器当前域名的子集
  rp: {
    id: 'duosecurity.dev',
    name: 'duosecurity',
  },
  // 用户信息
  user: {
    id: '5678',
    displayName: 'username',
    name: 'username',
  },
  timeout: 1000,
// 希望限制在单个身份验证器上为同一帐户创建多个凭据的依赖方使用。
  excludeCredentials: [
    {
      id: 'C0VGlvYFratUdAV1iCw-ULpUW8E-exHPXQChBfyVeJZCMfjMFcwDmOFgoMUz39LoMtCJUBW8WPlLkGT6q8qTCg',
      type: 'public-key',
      transports: ['internal'],
    },
  ],
};

返回的create()对象是一个包含公钥和用于验证注册事件的其他属性的对象。

PublicKeyCredential {
    // 新生成的凭证的ID;它将用于在认证用户时标识凭证。ID是一个base64编码的字符串
    id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...',
    // 属性返回包含内部槽中的标识符
    rawId: ArrayBuffer(59),
    response: AuthenticatorAttestationResponse {
        // 这表示从浏览器传递到身份验证器的数据,以便将新凭据与服务器和浏览器相关联。
        // 验证器将其作为UTF-8字节数组提供。
        clientDataJSON: ArrayBuffer(121),
        // 此对象包含凭证公钥、可选的证明证书和其他用于验证注册事件的元数据。
        // 它是以CBOR编码的二进制数据。
        attestationObject: ArrayBuffer(306),
    },
    type: 'public-key'
}

解析clientDataJSON数据:

// decode the clientDataJSON into a utf-8 string
const utf8Decoder = new TextDecoder('utf-8');
const decodedClientData = utf8Decoder.decode(
    credential.response.clientDataJSON)

// parse the string as an object
const clientDataObj = JSON.parse(decodedClientData);

console.log(clientDataObj)

{
    // 这是传递到create()调用中的challenge是否相同
    challenge: "p5aV2uHXr0AOqUk7HQitvi-Ny1....",
    // 服务器必须验证这个“origin”字符串与应用程序的源代码匹配。
    origin: "https://webauthn.guide",
    // 服务器验证该字符串是不是"webauthn.create",否则可能执行不正确的操作。
    type: "webauthn.create",
    // 客户端和callerOrigin之间的令牌绑定状态
    tokenBinding: {  // 如果缺失,表示客户端不支持令牌的绑定
        // status存在,则该成员必须存在,
        // 并且必须是与依赖方通信时使用的令牌绑定ID的base64url编码
        id:'ASAFDQWE12312aasDASD......';
        // supported 支持令牌绑定,但与依赖方通信时未协商
        // present 与依赖方通信时使用了令牌绑定,id必须存在
        status: 'present' | 'supported'';
    }

}

解析attestationObject:

// note: a CBOR decoder library is needed here.
const decodedAttestationObj = CBOR.decode(
    credential.response.attestationObject);

console.log(decodedAttestationObject);
{
    // 这里的authenticator数据是一个字节数组,包含有关注册事件的元数据,
    // 以及我们将用于未来身份验证的公钥。
    authData: Uint8Array(196),
    fmt: "fido-u2f",  // 表示证明格式
    attStmt: {
        sig: Uint8Array(70),  // 签名
        x5c: Array(1),        // X.509 格式编码的证明证书。
        alg: -7 // -7 使用SHA-256对应的哈希算法的摘要
    },
}

解析验证器数据:

const {authData} = decodedAttestationObject;

// get the length of the credential ID
const dataView = new DataView(
    new ArrayBuffer(2));
const idLenBytes = authData.slice(53, 55);
idLenBytes.forEach(
    (value, index) => dataView.setUint8(
        index, value));
const credentialIdLength = dataView.getUint16();

// get the credential ID
const credentialId = authData.slice(
    55, 55 + credentialIdLength);

// get the public key object
const publicKeyBytes = authData.slice(
    55 + credentialIdLength);

// the publicKeyBytes are encoded again as CBOR
const publicKeyObject = CBOR.decode(
    publicKeyBytes.buffer);
console.log(publicKeyObject)

{
    1: 2,
    3: -7,
    -1: 1,
    -2: Uint8Array(32) ...
    -3: Uint8Array(32) ...
}

authData是规范中描述的字节数组。解析它将涉及从数组中切片字节并将它们转换为可用的对象。

最后检索到的publicKeyObject是一个以COSE标准编码的对象,这是一种描述凭证公钥和使用它所需的元数据的简洁方式。

1: 1字段描述密钥类型。值2表示关键点类型为椭圆曲线格式。

3: 3字段描述用于生成身份验证签名的算法。-7值指示此身份验证器将使用ES256。

-1: -1字段描述此键的“曲线类型”。值1指示该键使用“P-256”曲线。

-2: -2字段描述此公钥的x坐标。

-3: -3字段描述此公钥的y坐标。

5.2使用WebAuthn凭据进行身份验证

注册完成后,现在可以对用户进行身份验证。在认证期间,创建断言,这是用户拥有私钥的证明。此断言包含使用私钥创建的签名。服务器使用在注册期间检索到的公钥来验证此签名。

navigator.credentials.get() 

在身份验证过程中,用户证明他们拥有他们注册的私钥。 它们通过提供一个assertion来实现,是在客户端上调用navigator.credentials.get()生成的。这将检索在注册期间生成的包含签名的凭证。

const credential = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions
});

publicKeyCredentialCreationOptions对象包含许多必需和可选字段,服务器指定这些字段为用户创建新凭证。

const publicKeyCredentialRequestOptions = {
    challenge: Uint8Array.from(
        randomStringFromServer, c => c.charCodeAt(0)),
    // 这个数组告诉浏览器服务器希望用户使用哪些凭据进行身份验证。
    allowCredentials: [{
        id: Uint8Array.from(
            credentialId, c => c.charCodeAt(0)),
        type: 'public-key',
        transports: ['usb', 'ble', 'nfc'], // 传输方式 USB、蓝牙,NFC
    }],
    timeout: 60000,
}

const assertion = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions
});

assertion调用返回的get()对象再次是PublicKeyCredential对象。它与我们在注册时收到的对象略有不同;特别是它包括signature成员,并且不包括公钥。

 
console.log(assertion);

PublicKeyCredential {
    id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...',
    rawId: ArrayBuffer(59),
    response: AuthenticatorAssertionResponse {
        // 认证器数据类似于注册期间接收的authData,但值得注意的是,这里不包括公钥。
        authenticatorData: ArrayBuffer(191),  
        clientDataJSON: ArrayBuffer(118), // 用作签名的一些数据
        signature: ArrayBuffer(70), // 私钥签名
        userHandle: ArrayBuffer(10),
    },
    type: 'public-key'
}

解析和验证身份验证数据:

获得认证后,将其发送到服务器进行验证。在验证数据完全有效之后,使用在注册期间存储在数据库中的公钥来验证签名。

const storedCredential = await getCredentialFromDatabase(
    userHandle, credentialId);

const signedData = (
    authenticatorDataBytes +
    hashedClientDataJSON);

const signatureIsValid = storedCredential.publicKey.verify(
    signature, signedData);

if (signatureIsValid) {
    return "签名校验成功! 🎉";
} else {
    return "签名校验失败. 😭"
}

6、 WebAuthn演示(注册和认证)

 有关WebAuthn的演示,请访问https//webauthn.io/https://webauthn.io/

6.1 注册演示:

用Chrome 浏览器进行测试,第一步:输入用户名(zj),单击“Register” 按钮。用电脑测试,需打开蓝牙。

 

然后输入电脑密码,则提示验证成功! 

6.2 认证演示:

1、输入用户名,点击“”按钮,进行认证,登录成功,如下所示:

6.3 浏览器中存储

在Chrome浏览器中,点击“设置”->“自动填充”->“密码管理”可以找到对应的通用密钥,如下图所示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/538960.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IO 与进程线程

IO进程 scanf\printf:终端 IO:input/output,文件 标准IO 文件IO 文件属性获取:ls -l 文件类型 文件权限 链接数 用户名 组名 大小 时间 文件名 目录操作:ls 库 进程 进程:创建进程 线程:创建线程…

36、Adaptive Forms(4)Create Adapt Form

文章目录 36、Adaptive Forms(4)Create Adapt FormData Model服务配置创建Adapt Form Template创建Adapt Form 36、Adaptive Forms(4)Create Adapt Form DataModel创建好后,就可以进行数据的获取和存储。 Data Model…

抖音seo源码开发/技术自研底层逻辑

抖音seo霸屏,是一种专为抖音视频创作者和传播者打造的视频批量剪辑,批量分发产品。使用抖音seo霸屏软件,可以帮助用户快速高效的制作出高质量的优质视频。 使用方法:1. 了解用户的行为习惯 2. 充分利用自身资源进行开发 3. 不…

开心档之Java 基本数据类型

Java 基本数据类型 目录 Java 基本数据类型 内置数据类型 实例 实例 类型默认值 实例 引用类型 Java 常量 自动类型转换 自动类型转换 实例 强制类型转换 实例 隐含强制类型转换 变量就是申请内存来存储值。也就是说,当创建变量的时候,需…

4.5.1 虚拟局域网(一)

4.5.1 虚拟局域网(一) 一、虚拟局域网的划分 虚拟局域网的划分是非常灵活的,可以根据端口进行划分,也可以根据MAC地址进行划分,也可以根据网络层协议进行划分甚至根据IP组播进行划分。 (一)基…

百度工程师移动开发避坑指南——内存泄漏篇

作者 | 启明星小组 在日常编写代码时难免会遇到各种各样的问题和坑,这些问题可能会影响我们的开发效率和代码质量,因此我们需要不断总结和学习,以避免这些问题的出现。接下来我们将围绕移动开发中常见问题做出总结,以提高大家的开…

【Prompting】ChatGPT Prompt Engineering开发指南(6)

ChatGPT Prompt Engineering开发指南:Expanding/The Chat Format Expanding自定义对客户电子邮件的自动回复提醒模型使用客户电子邮件中的详细信息 The Chat Format总结内容来源 在本教程中,第一部分学习生成客户服务电子邮件,这些电子邮件是…

前端(HTML)

网络传输三大基石:URL,HTTP,HTML 前端使用URL利用HTTP协议去向服务器端发送请求某个资源,服务器端响应浏览器一个HTML页面,浏览器对HTML页面解析 HTML的标准结构: 【1】先用普通文本文档新建一个文本,将文本的后缀改为.html 或者 .htm 我…

chatgpt赋能Python-mac版的python怎么用

Mac版Python的使用指南 Python是一种高级编程语言,常用于Web开发、数据分析、机器学习等领域。在Mac系统上,Python的安装和使用非常方便。本文将介绍如何在Mac上安装和使用Python并演示几个常见的Python用例。 Python在Mac上的安装 Mac电脑自带Python…

springboot+jsp+javaweb学生信息管理系统 05hs

springboot是基于spring的快速开发框架, 相比于原生的spring而言, 它通过大量的java config来避免了大量的xml文件, 只需要简单的生成器便能生成一个可以运行的javaweb项目, 是目前最火热的java开发框架 (1)管理员模块:系统中的核心用户是管…

蓝牙耳机什么牌子的好用?发烧友实测2023年蓝牙耳机排名

从AirPods入坑蓝牙耳机开始,断断续续已经买过二十多款蓝牙耳机了,我每天都会逛逛数码板块,最近看到了2023年蓝牙耳机排名,为检验是否名副其实,我购入了排名前五的品牌进行了一个月的测试,接下来我就来分享一…

k8s系列(四)——资源对象

k8s系列四——资源对象 pod概念 思考:为什么k8s会引出pod这个概念,容器不能解决么? 我的理解:一组密切相关的服务使用容器的话,如果他们的镜像不在一个容器里的话,那么就需要配置反向代理进行通信&#xf…

Packet Tracer – 配置中继

Packet Tracer – 配置中继 地址分配表 设备 接口 IP 地址 子网掩码 交换机端口 VLAN PC1 NIC 172.17.10.21 255.255.255.0 S2 F0/11 10 PC2 NIC 172.17.20.22 255.255.255.0 S2 F0/18 20 PC3 NIC 172.17.30.23 255.255.255.0 S2 F0/6 30 PC4 NIC 1…

java 根据指定字段排序(mysql)

需求: 查询数据的时候,由前端指定字段和排序方式进行排序。 这时候要怎么做呢? 要定义一个相应的类,排序的时候,是动态拼接的。 要考虑多个字段,不同排序方式的情况。 处理 OrderField import io.swagge…

基于matlab的ADC输入动态范围测量代码

如图,本文主要分享基于matlab的ADC输入数据有效位分析的代码。 fidfopen(C:\Users\Administrator\Desktop\Test.txt,r); % numptinput(Data Record Size (Number of Points)? );% fclkinput(Sampling Frequency (MHz)? ); numpt16384; fclk50; numbit14; [v1]fs…

SDK案例记录

目前的极简配置 注意事项 默认的属性配置中,大多采用环境变量的形式,方便不同设备通用 比如“常规”->“输出目录”为 $(SolutionDir)..\bin\win_msvc2017$(Platform)\$(Configuration)\案例运行前的配置(除MwBatchSimPlugin&#xff0…

华丽家族股东大会21项议案全被否

5月17日晚间,A股上市公司华丽家族发布关于收到上海证券交易所监管工作函的公告,交易所对相关事项提出监管要求。 在此之前,华丽家族当天召开股东大会,21项股东大会议案全部未通过。历史上,股东大会议案全部被否的情形…

『python爬虫』24. selenium之无头浏览器-后台静默运行(保姆级图文)

目录 1. 无头浏览器2. 分析被爬取数据的网页结构3. 完整代码总结 欢迎关注 『python爬虫』 专栏,持续更新中 欢迎关注 『python爬虫』 专栏,持续更新中 1. 无头浏览器 一般性的selenium会打开浏览器页面,展示图形化页面给我们看,我…

Spring Boot注入Servlet、Filter、Listener 注解方式和使用RegistrationBean二种方式 加源码分析

目录 Spring Boot 注入Servlet、Filter、Listener 官方文档 基本介绍 应用实例1-使用注解方式注入 创建/Servlet_.java 修改Application.java , 加入ServletComponentScan 完成测试 创建Filter_.java 创建static/css/t.css, 作为测试文件 完成测试, 注意观察后台 注…

【数据结构】--- 博主拍了拍你并向你扔了一“棵”二叉树(概念+结构)

文章目录 前言🌟一、树概念及结构:🌏1.1树的概念:🌏1.2树的相关概念:🌏1.3树的表示:💫1.3.1左孩子右兄弟表示法:💫1.3.2双亲表示法: &…