webauthn登录实现

1,071 阅读1分钟

1.流程图:

2.转码的函数

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;
}

3.落地

分为三个部分: 客户端 ,服务器, 登录因子(硬件设备fidokey,指纹,密码)

3.1 注册

用户点击登录按钮向服务器发送了一个http/https请求获取挑战challenge(一个base64的字符串);调用navigator.credentials.create(createCredentialDefaultArgs)方法,此时浏览器会弹出提示,请求用户的授权,授权成功客户端得到签名的challenge加上新的身份信息cred发送给服务器,服务器返回信息是否注册成功。

     const createCredentialDefaultArgs = {
            publicKey: {
                // Relying Party (a.k.a. - Service):
                rp: {
                      id: data.rp_id,
                      name: 'Wax FTW'
                },
            user: {
                id: new Uint8Array(16),
                name: data.user,
                displayName: data.user
            },
            
            origin: data.origin,
            
            pubKeyCredParams: [{
                type: "public-key",
                alg: -7
            }],
            // attestation: "direct",
            attestation:"none",
            // timeout: 60000,
            challenge: _base64ToArrayBuffer(data.challenge)
        }
    }
    // 发送给服务的参数信息以及转码规则
    navigator.credentials.create(createCredentialDefaultArgs)
    .then((cred) => {
        const transports = cred.response.getTransports()[0];// 登录因子类型
        axios.get('https://www.xxx.xxx/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);
    });

3.2 登录

与注册的流程是一致的,先向服务器获取挑战,调用navigator.credentials.get()方法,客服端授权,获取签名的挑战,将这个挑战发送给服务器

axios.get('https://xxx.xxx/webauthn_login',{
  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://xxx.xxx/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)=>{
           	console.log('webauthn login succ')
        }).catch(err=>{
           	console.log('webauthn login fail')
        })
    }).catch((err)=>{
        console.log(err, '未授权或者授权失败');
    })
}).catch((err)=>{
   console.log('获取挑战失败')
})