前端如何实现硬件登陆和指纹登录

292 阅读2分钟

写在前面,World Wide Web Consortium (W3C) 宣布 Web Authentication API (WebAuthn) 成为正式的 Web 标准。WebAuthn 现在是一项无密码登录验证的开放标准,为 Web 应用和服务提供了无密码的公钥身份认证接口,获得了 Airbnb、阿里巴巴、Apple、Google、IBM、Intel,、Microsoft、Mozilla、PayPal、SoftBank、腾讯和  Yubico 等公司的支持,允许用户使用生物识别、移动设备等登录在线账号。主流浏览器如 Google Chrome、Mozilla Firefox 和 Microsoft Edge 都已经在去年加入了对 WebAuthn 的支持,苹果的 Safari 浏览器则在预览版中加入了对 WebAuthn 的支持。

W3C的WebAuthn推荐FIDO 联盟的FIDO2规范集的核心组成部分。FIDO2是支持公钥加密和多因素身份验证的标准 - 特别是通用身份验证框架(UAF)和通用第二因子(U2F)协议。为了促进采用,FIDO联盟提供测试工具和认证计划

  FIDO2尝试以四种方式解决传统身份验证问题:

安全性:FIDO2加密登录凭证在每个网站都是唯一的;生物识别或密码等其他机密永远不会离开用户的设备,也永远不会存储在服务器上。此安全模型消除了网络钓鱼及所有形式的密码被盗和重放攻击的风险。

便利性:用户使用指纹识别器、相机、FIDO安全密钥或个人移动设备等简单方法登录。

隐私:由于FIDO密钥对于每个互联网站点都是唯一的,因此它们不能用于跨站点跟踪用户。

可扩展性:网站可以通过API调用,跨平台支持数十亿消费者常用设备上的浏览器和平台。

下面让我们看下怎么实际应用,使用浏览器提供的方法 navigator.credentials.create

注册

image.png

登录

image.png

代码实现

import axios from 'axios';
function _base64ToArrayBuffer(base64) {
  var binary_string =  window.atob(base64);
  var len = binary_string.length;
  var bytes = new Uint8Array( len );
  for (var i = 0; i < len; i++)        {
      bytes[i] = binary_string.charCodeAt(i);
  }
  return bytes.buffer;
}
function _arrayBufferToBase64( buffer ) {
  var binary = '';
  var bytes = new Uint8Array( buffer );
  var len = bytes.byteLength;
  for (var i = 0; i < len; i++) {
    binary += String.fromCharCode( bytes[ i ] );
  }
  return window.btoa( binary );
}

function _arrayBufferToString( buffer ) {
  var binary = '';
  var bytes = new Uint8Array( buffer );
  var len = bytes.byteLength;
  for (var i = 0; i < len; i++) {
    binary += String.fromCharCode( bytes[ i ] );
  }
  return binary;
}

const loginApi = 'https://www.baidu.com/login';
const registerApi = 'https://www.baidu.com/register';

const Action = {
  register:(token) => {
      return (dispatch, getState) =>{
          axios.get(registerApi,{
              params : {
                token,
                origin: window.location.origin
              }
          }).then(({data})=>{
              const challenge = _base64ToArrayBuffer(data.challenge);
      
              const createCredentialDefaultArgs = {
                publicKey: {
                    rp: {
                      id: data.rp_id,
                      name: 'Wax FTW'
                    },
                    origin: data.origin,
                    user: {
                      id: new Uint8Array(16),
                      name: data.user,
                      displayName: data.user
                    },
                    pubKeyCredParams: [{
                        type: "public-key",
                        alg: -7
                    }],
                    // attestation: "direct",
                    attestation:"none",
                    // timeout: 60000,
                    challenge
                }
            };
              navigator.credentials.create(createCredentialDefaultArgs)
              .then((cred) => {
                  const transports = cred.response.getTransports()[0];
                  axios.get('https://owaf.io/v2api/webauthn_register2',{
                    params : {
                      type: cred.type,
                      rawID: _arrayBufferToBase64(cred.rawId),
                      clientDataJSON: _arrayBufferToString(cred.response.clientDataJSON),
                      sref:data.sref,
                      token,
                      transports,
                      attestationObject:_arrayBufferToBase64(cred.response.attestationObject)
                    }
                  }).then((res)=>{
                    console.log(res,'success register')
                  })
              })
              .then((assertion) => {
                  console.log(assertion,'hello world')
                  // console.log("ASSERTION", assertion);
              })
              .catch((err) => {
                  console.log('create defeat')
                  // console.log("ERROR", err);
              });
          }).catch((err)=>{
              console.log(err,'get chanllage defeat')
          })
      }
  },
  login: (email)=>{
    return (dispatch,getState) => new Promise((resolve, reject) => {
        axios.get(loginApi,{
          params:{
            email,
            origin: window.location.origin
          }
        }).then(res=>{
          const data = JSON.parse(res.data.data)
          const allowCredentials = data.cred_ids.map(function(x){
            return {
                id: _base64ToArrayBuffer(x),
                type: "public-key",
                transports: ['usb','nfc','ble'] // internal
            }
          })
        const sref = res.data.sref;
        navigator.credentials.get({
          publicKey: {
            challenge: _base64ToArrayBuffer(data.challenge),
            allowCredentials
          }
        }).then((newCredential)=>{     
          axios.get('https://owaf.io/v2api/webauthn_login2',{
            params:{
              sref,
              rawID: _arrayBufferToBase64(newCredential.rawId),
              type: newCredential.type,
              clientDataJSON: _arrayBufferToString(newCredential.response.clientDataJSON),
              authenticatorData: _arrayBufferToBase64(newCredential.response.authenticatorData),
              sig: _arrayBufferToBase64(newCredential.response.signature)
            }
          }).then((res)=>{
            localStorage.setItem('token',res.data.token) // save
            localStorage.setItem('email',email);
            dispatch({
              type: 'webauthnlogin',
              payload: {
                info: true // login success
              }
            })
            resolve('webauthn login succ')
          }).catch(err=>{
            dispatch({
              type: 'webauthnlogin',
              payload: {
                info: false // login error
              }
            })
          })
  
        }).catch((err)=>{
          console.log(err);
        })
        }).catch((err)=>{
          dispatch({
            type: 'webauthnlogin',
            payload: {
              info: false
            }
          })
          reject('invalid email')
        })
      })
    },
}
export default Action

windows效果预览图

mdn 地址 developer.mozilla.org/en-US/docs/…