webauthn实现PIN码和硬件的双重身份验证

描述:这次的需求是这样的:后台管理系统之中有一个非常重要的功能,这个功能的操作权限只能有唯一的一个人来做,其他人就是有可查看这个菜单的权限,没有硬件和生物验证也没办法做此操作

实现步骤

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>

将JSON文件引入

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

你可能感兴趣的:(web3,前端,vue.js,javascript)