vue中使用WebAuthn
着魔的村支 2024-08-09 13:03:01 阅读 73
## WebAuthn
* 全称是 Web Authentication,是一个由万维网联盟(W3C)和 FIDO 联盟(Fast Identity Online)开发的 web 标准。它允许网站和应用程序使用公钥加密和生物识别技术(如指纹识别、面部识别)进行强身份验证,以替代传统的密码登录方式。WebAuthn 提供了更高的安全性和更好的用户体验。
* <https://webauthn.io/>
### WebAuthn 工作原理
WebAuthn 是一个由万维网联盟(W3C)和 FIDO 联盟(Fast Identity Online)开发的 web 标准,它允许网站和应用程序使用公钥加密和生物识别技术(如指纹识别、面部识别)进行强身份验证,以替代传统的密码登录方式。以下是 WebAuthn 的工作原理,包括注册(Registration)和认证(Authentication)两个主要过程。
![WebAuthn Authentication](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/704bd11de6314dfeb579c4f515cec895~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzQ2NjExNzQzNjY3MTcwMyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1722396857&x-orig-sign=aMT4vRfxz2rCeDPjbWTF5AEjny0%3D)
### 1. 注册(Registration)
在注册过程中,用户在其设备上创建一个新的公钥-私钥对,并将公钥发送给服务器保存。具体步骤如下:
1. **用户发起注册请求**:用户在网站上选择注册,并开始注册过程。
2. **服务器生成挑战(challenge)**:服务器生成一个随机的挑战,并将其发送给用户的浏览器。
3. **浏览器调用 WebAuthn API**:浏览器使用 WebAuthn API 调用用户设备的可信平台模块(TPM)或安全元件,生成公钥-私钥对。然后,设备会用私钥签署服务器的挑战,并将签名和公钥一起发送回服务器。
4. **服务器验证并存储公钥**:服务器验证签名和挑战,以确保请求的真实性。如果验证成功,服务器存储公钥用于后续的认证。
### 2. 认证(Authentication)
在认证过程中,用户使用之前注册的设备进行登录。具体步骤如下:
1. **用户发起登录请求**:用户在网站上选择登录,并开始认证过程。
2. **服务器生成挑战(challenge)**:服务器生成一个随机的挑战,并将其发送给用户的浏览器。
3. **浏览器调用 WebAuthn API**:浏览器使用 WebAuthn API 调用用户设备的 TPM 或安全元件,使用私钥签署服务器的挑战。然后,设备将签名和相关信息发送回服务器。
4. **服务器验证签名**:服务器使用存储的公钥验证签名和挑战,以确保请求的真实性。如果验证成功,用户成功登录。
### WebAuthn 的优势
1. **提高安全性**:WebAuthn 使用公钥加密,减少了传统密码的攻击面,避免了密码泄露、弱密码等问题。
2. **简化用户体验**:利用设备的生物识别功能,如指纹、面部识别等,使得用户无需记住复杂的密码,登录过程更加便捷。
3. **广泛支持**:WebAuthn 标准得到了主流浏览器和操作系统的广泛支持,包括 Chrome、Firefox、Edge、Safari,以及 Android 和 iOS 设备。
## navigator.credentials.create
- [`Navigator`](https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator) 接口的只读属性 **`credentials`** 返回与当前文档关联的 [`CredentialsContainer`](https://developer.mozilla.org/zh-CN/docs/Web/API/CredentialsContainer) 对象,该对象暴露用于请求凭据的方法。[`CredentialsContainer`](https://developer.mozilla.org/zh-CN/docs/Web/API/CredentialsContainer) 接口还会在发生感兴趣的事件时通知用户代理,例如成功登录或注销。此接口可用于特性检测。
### 开始注册
#### Web Authentication API 的 options 对象
| 字段 | 类型 | 说明 | 可选值 |
| --------------------------- | ------- | -------------------------------------------------------------------------------------- | ------ |
| **attestation** | 字符串 | 指定所需的认证声明传递偏好。 | `"none"`(无需认证信息)、`"indirect"`(间接认证)、`"direct"`(直接认证) |
| **authenticatorSelection** | 对象 | 定义选择认证器的标准。 | |
| - authenticatorAttachment | 字符串 | 指定要使用的认证器类型。 | `"platform"`(使用集成到设备中的认证器)、`"cross-platform"`(使用外部认证器) |
| - requireResidentKey | 布尔值 | 指示认证器是否必须创建存储在设备上的常驻密钥。 | `true`(是)、`false`(否) |
| - residentKey | 字符串 | 指定常驻密钥的要求。 | `"required"`(必须)、`"preferred"`(优先)、`"discouraged"`(不建议) |
| **challenge** | 字符串 | 用于认证过程中的挑战码,应该是从服务器端生成的。 | |
| **excludeCredentials** | 数组 | 列出在注册新凭证时应排除的凭证的列表,用于确保不重复注册。 | |
| **extensions** | 对象 | 用于扩展 Web Authentication API 的附加选项。 | |
| - credProps | 布尔值 | 指示是否返回有关创建的凭证的属性信息。 | `true`(返回),`false`(不返回) |
| **pubKeyCredParams** | 数组 | 定义公钥证书应使用的加密算法。 | |
| - alg | 数字 | 算法标识符(如 `-7` 表示 ES256,`-257` 表示 RS256) | |
| - type | 字符串 | 证书类型,通常为 `"public-key"` | |
| **rp** | 对象 | 定义依赖方(即服务提供者)的信息。 | |
| - id | 字符串 | 依赖方的域名。 | |
| - name | 字符串 | 依赖方的描述名称,如 `"Passkey Example"` 本地测试可以用 `window.location.hostname` | |
| **timeout** | 数字 | 指定操作的超时时间(毫秒)。 | |
| **user** | 对象 | 定义正在注册或认证的用户的信息。 | |
| - displayName | 字符串 | 用户的显示名称。 | |
| - id | 字符串 | 用户的唯一标识符。 | |
| - name | 字符串 | 用户的名称。 | |
``` json
// options 参考
{
attestation: "none",
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
residentKey: "required",
},
challenge: "F16NrxGy23Ps_73Pgy_H6fh0ihgEXBuycusFLahMvmU",
excludeCredentials: [],
extensions: {
credProps: true,
},
pubKeyCredParams: [
{
alg: -7, // 对应于ES256
type: "public-key",
},
{
alg: -257, // 对应于RS256
type: "public-key",
},
],
rp: {
id: window.location.hostname, // 本地测试用 可以使用localhost
name: "Passkey Example",
},
timeout: 60000,
user: {
displayName: "allen",
id: "20222027",
name: "allen",
},
};
```
#### 需要将 challenge 和 user.id 转成`ArrayBuffer` 类型
```javascript
options.challenge = base64url.decode(options.challenge);
options.user.id = base64url.decode(options.user.id);
const base64url = {
encode: function (buffer) {
const base64 = window.btoa(String.fromCharCode(...new Uint8Array(buffer)));
return base64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
},
decode: function (base64url) {
const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
const binStr = window.atob(base64);
const bin = new Uint8Array(binStr.length);
for (let i = 0; i < binStr.length; i++) {
bin[i] = binStr.charCodeAt(i);
}
return bin.buffer;
},
};
```
#### 唤起生物认证
- 未开启 windows hello
![image.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/aec727f881714ec58801841a0ba0c62f~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzQ2NjExNzQzNjY3MTcwMyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1722396857&x-orig-sign=WXYP2zI72PEkjw2ZkdTTF9hH3Ls%3D)
- 在系统设置里面添加
![image.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/2ee34e6b32fc4140ac0cd14711021973~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzQ2NjExNzQzNjY3MTcwMyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1722396857&x-orig-sign=bZyPHmN3VutfxNbcyElmBH%2F%2FKW4%3D)
- 添加完成 唤起成功
![image.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/1afaed40e72e4187810ba3fdec7cbc63~tplv-73owjymdk6-watermark.image?policy=eyJ2bSI6MywidWlkIjoiMzQ2NjExNzQzNjY3MTcwMyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1722396857&x-orig-sign=cTtPDP6hdtsaFxP48dY41CrpTJQ%3D)
#### 完成注册后的步骤
在 Web Authentication API (`navigator.credentials.create`) 成功创建公钥凭证后,你需要执行以下几个关键步骤以完成用户的注册过程:
#### 1. 发送凭证信息到服务器
成功创建凭证后,需要将其详细信息发送到服务器端进行验证和存储。以下是应发送的凭证详情:
- **ID (`credential.id`)**: 凭证的唯一标识符。
- **类型 (`credential.type`)**: 凭证的类型,通常是 `"public-key"`。
- **原始 ID (`credential.rawId`)**: 凭证的原始标识符,使用 Base64URL 编码格式。
- **响应 (`credential.response`)**: 包括认证对象 (`attestationObject`) 和客户端数据 (`clientDataJSON`) 的响应,均使用 Base64URL 编码。
#### 2. 服务器端验证
服务器接收到客户端发送的凭证信息后,需要执行以下验证步骤:
- **验证挑战** (`challenge`): 确认返回的挑战码与之前发送给客户端的挑战码相匹配。
- **验证来源** (`origin`): 确认凭证的创建请求来自预期的域名。
- **验证公钥凭证参数** (`pubKeyCredParams`): 确认凭证使用了有效的加密算法。
- **存储凭证信息**: 在服务器端安全地存储用户的公钥和其他相关凭证信息,以备将来进行身份验证使用。
#### 示例代码:将凭证信息发送到服务器
```javascript
fetch('/api/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: credential.id,
type: credential.type,
rawId: credential.rawId,
attestationObject: credential.response.attestationObject,
clientDataJSON: credential.response.clientDataJSON
})
})
.then(response => response.json())
.then(data => {
console.log('注册成功', data);
alert('注册成功!');
})
.catch(error => {
console.error('注册失败', error);
alert('注册失败,请重试!');
});
```
#### 完整注册代码
```javascript
async register() {
if (!("credentials" in navigator)) {
alert(
"此浏览器不支持 Web Authentication API。请尝试更新浏览器或使用其他支持的浏览器。"
);
return;
}
try {
// 从服务器获取创建凭证的选项
const {data: options} = await axios.get('/api/register-options');
// 转成 arrayBuffer
options.challenge = base64url.decode(options.challenge);
options.user.id = base64url.decode(options.user.id);
// 开始注册
const credential = await navigator.credentials.create({
publicKey: options,
});
await axios.post('/api/register', {
id: credential.id,
type: credential.type,
rawId: credential.rawId,
attestationObject: credential.response.attestationObject,
clientDataJSON: credential.response.clientDataJSON
});
alert("注册成功");
} catch (error) {
console.error("注册失败", error);
alert("注册失败");
}
},
```
上一篇: jenkins配置giteewebhook触发流水线部署前后端SpringbootVue,nginx部署,jar包shell脚本,企业微信推送shell脚本配置,devopsLinux 集成式触发部署
下一篇: 代码:前端与数据库交互的登陆界面
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。