描述:这次的需求是这样的:后台管理系统之中有一个非常重要的功能,这个功能的操作权限只能有唯一的一个人来做,其他人就是有可查看这个菜单的权限,没有硬件和生物验证也没办法做此操作
1,使用webauthn注册和账号的绑定以及验证
如果你需要使用将下面的代码放到你自己新建的JS文件中,引入即可使用
// src/webauthn-json/base64url.ts
function base64urlToBuffer(baseurl64String) {
const padding = "==".slice(0, (4 - baseurl64String.length % 4) % 4);
const base64String = baseurl64String.replace(/-/g, "+").replace(/_/g, "/") + padding;
const str = atob(base64String);
const buffer = new ArrayBuffer(str.length);
const byteView = new Uint8Array(buffer);
for (let i = 0; i < str.length; i++) {
byteView[i] = str.charCodeAt(i);
}
return buffer;
}
function bufferToBase64url(buffer) {
const byteView = new Uint8Array(buffer);
let str = "";
for (const charCode of byteView) {
str += String.fromCharCode(charCode);
}
const base64String = btoa(str);
const base64urlString = base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
return base64urlString;
}
// src/webauthn-json/convert.ts
var copyValue = "copy";
var convertValue = "convert";
function convert(conversionFn, schema2, input) {
if (schema2 === copyValue) {
return input;
}
if (schema2 === convertValue) {
return conversionFn(input);
}
if (schema2 instanceof Array) {
return input.map((v) => convert(conversionFn, schema2[0], v));
}
if (schema2 instanceof Object) {
const output = {};
for (const [key, schemaField] of Object.entries(schema2)) {
if (schemaField.deriveFn) {
const v = schemaField.deriveFn(input);
if (v !== void 0) {
input[key] = v;
}
}
if (!(key in input)) {
if (schemaField.required) {
throw new Error(`Missing key: ${key}`);
}
continue;
}
if (input[key] == null) {
output[key] = null;
continue;
}
output[key] = convert(conversionFn, schemaField.schema, input[key]);
}
return output;
}
}
function derived(schema2, deriveFn) {
return {
required: true,
schema: schema2,
deriveFn
};
}
function required(schema2) {
return {
required: true,
schema: schema2
};
}
function optional(schema2) {
return {
required: false,
schema: schema2
};
}
// src/webauthn-json/basic/schema.ts
var publicKeyCredentialDescriptorSchema = {
type: required(copyValue),
id: required(convertValue),
transports: optional(copyValue)
};
var simplifiedExtensionsSchema = {
appid: optional(copyValue),
appidExclude: optional(copyValue),
credProps: optional(copyValue)
};
var simplifiedClientExtensionResultsSchema = {
appid: optional(copyValue),
appidExclude: optional(copyValue),
credProps: optional(copyValue)
};
var credentialCreationOptions = {
publicKey: required({
rp: required(copyValue),
user: required({
id: required(convertValue),
name: required(copyValue),
displayName: required(copyValue)
}),
challenge: required(convertValue),
pubKeyCredParams: required(copyValue),
timeout: optional(copyValue),
excludeCredentials: optional([publicKeyCredentialDescriptorSchema]),
authenticatorSelection: optional(copyValue),
attestation: optional(copyValue),
extensions: optional(simplifiedExtensionsSchema)
}),
signal: optional(copyValue)
};
var publicKeyCredentialWithAttestation = {
type: required(copyValue),
id: required(copyValue),
rawId: required(convertValue),
response: required({
clientDataJSON: required(convertValue),
attestationObject: required(convertValue),
transports: derived(copyValue, (response) => response.getTransports?.() || [])
}),
clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults())
};
var credentialRequestOptions = {
mediation: optional(copyValue),
publicKey: required({
challenge: required(convertValue),
timeout: optional(copyValue),
rpId: optional(copyValue),
allowCredentials: optional([publicKeyCredentialDescriptorSchema]),
userVerification: optional(copyValue),
extensions: optional(simplifiedExtensionsSchema)
}),
signal: optional(copyValue)
};
var publicKeyCredentialWithAssertion = {
type: required(copyValue),
id: required(copyValue),
rawId: required(convertValue),
response: required({
clientDataJSON: required(convertValue),
authenticatorData: required(convertValue),
signature: required(convertValue),
userHandle: required(convertValue)
}),
clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults())
};
var schema = {
credentialCreationOptions,
publicKeyCredentialWithAttestation,
credentialRequestOptions,
publicKeyCredentialWithAssertion
};
// src/webauthn-json/basic/api.ts
function createRequestFromJSON(requestJSON) {
return convert(base64urlToBuffer, credentialCreationOptions, requestJSON);
}
function createResponseToJSON(credential) {
return convert(bufferToBase64url, publicKeyCredentialWithAttestation, credential);
}
async function create(requestJSON) {
const credential = await navigator.credentials.create(createRequestFromJSON(requestJSON));
return createResponseToJSON(credential);
}
function getRequestFromJSON(requestJSON) {
return convert(base64urlToBuffer, credentialRequestOptions, requestJSON);
}
function getResponseToJSON(credential) {
return convert(bufferToBase64url, publicKeyCredentialWithAssertion, credential);
}
async function get(requestJSON) {
const credential = await navigator.credentials.get(getRequestFromJSON(requestJSON));
return getResponseToJSON(credential);
}
// src/webauthn-json/basic/supported.ts
function supported() {
return !!(navigator.credentials && navigator.credentials.create && navigator.credentials.get && window.PublicKeyCredential);
}
export {
create,
get,
schema,
supported
};
//# sourceMappingURL=webauthn-json.js.map
这算是准备工作完成
2,下面是实际代码的实现
<el-button id="registerButton"
@click="register">
register
</el-button>
<el-button id="authenticateButton"
@click="authenticate">
Authenticate
</el-button>
import * as webauthnJson from './lib/webauthn-json.js'
(1)注册
// 注册 register
async register () {
var registerRequest
//发送注册请求的接口
const registerJson = await this.$store.dispatch('userStore/webauthnRegister').then((res) => {
registerRequest = res.data
return this.executeRegisterRequest(res.data)
}).finally(() => { })
// 注册验证接口
this.$store.dispatch('userStore/webauthnVerifyRegister', {
requestId: registerRequest.requestId,
credential: registerJson
}).then((res) => {
if (res.code === 0) {
this.$message.success('Register successfully')
}
})
},
// 注册弹框的关键所在
executeRegisterRequest (request) {
let registerArg = {
...request.publicKeyCredentialCreationOptions,
extensions: {
credProps: true
}
}
//registerArg 这里面的参数 前端设置和后端设置都是可以的,我这里是后端设置的,如果不能弹框,着重检查这里的参数设置,rp:{id:'xxxxxx'},这个id是要跟着你实际访问的域名走的,如果你本地调试localhose:8080,id就是localhost
return webauthnJson.create({ publicKey: registerArg })
},
(2),登录验证,步骤和注册一样,代码也是差不多
// login
async authenticate () {
var loginRequest
//发送登录请求的接口
const loginJson = await this.$store.dispatch('userStore/webauthnLogin').then((res) => {
loginRequest = res.data
return this.executeAuthenticateRequest(res.data)
}).finally(() => { })
// 登录验证接口
this.$store.dispatch('userStore/webauthnVerifyLogin', {
requestId: loginRequest.requestId,
credential: loginJson
}).then((res) => {
this.$message.success('Authenticate successfully')
})
}
},
// 验证弹框的关键所在
executeAuthenticateRequest (request) {
letloginArg = {
...request.publicKeyCredentialRequestOptions,
extensions: {}
}
return webauthnJson.get({ publicKey: loginArg })
}
参考文章:https://flyhigher.top/develop/2160.html
源码地址: https://github.com/Yubico/java-webauthn-server