我在ConsenSys为各种客户构建了大量的概念证明,通常他们想要利用以太坊区块链来解决某些业务用例。奇怪的是,这些系统通常设计有标准的网络登录(即用户名和密码)。我总是问自己为什么我还在这样做设计,毕竟,这是今天以太网目前可以解决每个烦人的Web应用程序的一个方面。所以我决定停下脚步,设计一下这个解决方案。

JSON Web token

登录标准Web系统(和/或使用其API)的一种非常流行的方法是将密码(经过哈希的客户端)提交给认证端点并接收token作为回报。这通常称为JSON Web Token,通常在一段有限的时间内(几分钟到几天)有效。这是一个关于标准实现的很好的教程。

JSON Web Token很好,我开始认为在区块链上验证自己很容易。事实上,当你使用以太坊时,你需要不断地去改进。

如果你将以太网地址(这只是公钥的sha3哈希)视为网站上的帐户,则可以通过使用私钥对一段数据进行签名来证明你拥有该帐户,这非常容易。此数据是任意的,可以是网站API提供的任意随机字符串。因此,我们可以使用地址作为用户名并绕过密码的需要。事实上,我们甚至不需要使用区块链来做到这一点。

这是使用Express的样子:

首先,我们需要使用私钥进行椭圆曲线签名:

var ethUtil = require(‘ethereumjs-util’);  // >=5.1.1
var data = ‘i am a string’;
// Elliptic curve signature must be done on the Keccak256 Sha3 hash of a piece of data.
var message = ethUtil.toBuffer(data);
var msgHash = ethUtil.hashPersonalMessage(message);
var sig = ethUtil.ecsign(msgHash, privateKey);
var serialized = ethUtil.bufferToHex(this.concatSig(sig.v, sig.r, sig.s))
return serialized

不要过分担心这些参数是什么。这里有一些密码学,我鼓励你阅读椭圆曲线签名。比特币维基是一个不错的起点。

无论如何,一旦我们有了签名组件,我们就可以将它们与用户的地址一起打包并将其全部发送到认证端点。

POST/Authenticate

var jwt = require(‘jsonwebtoken’);
var ethUtil = require('ethereumjs-util');
function checkSig(req, res) {
  var sig = req.sig;
  var owner = req.owner;
  // Same data as before
  var data = ‘i am a string’;
  var message = ethUtil.toBuffer(data)
  var msgHash = ethUtil.hashPersonalMessage(message)
  // Get the address of whoever signed this message
  var signature = ethUtil.toBuffer(sig)
  var sigParams = ethUtil.fromRpcSig(signature)
  var publicKey = ethUtil.ecrecover(msgHash, sigParams.v, sigParams.r, sigParams.s)
  var sender = ethUtil.publicToAddress(publicKey)
  var addr = ethUtil.bufferToHex(sender)

  // Determine if it is the same address as 'owner'
  var match = false;
  if (addr == owner) { match = true; }
  if (match) {
    // If the signature matches the owner supplied, create a
    // JSON web token for the owner that expires in 24 hours.
    var token = jwt.sign({user: req.body.addr}, ‘i am another string’,  { expiresIn: “1d” });
    res.send(200, { success: 1, token: token })
  } else {
    // If the signature doesn’t match, error out
    res.send(500, { err: ‘Signature did not match.’});
  }
}

所以基本上,给定一些数据,一个地址和一个EC签名的组件,我们可以安全的证明该地址属于签署数据的人。很酷,对吧?

一旦我们对签名和地址匹配感到满意,我们就可以为该地址服务器端签署一个JSON Web token。 在这种情况下,token有效期为1天。

现在我们只需要放入一些中间件来保护任何服务或修改受保护信息的路由。

middleware/auth.js

function auth(req, res, next) {
  jwt.verify(req.body.token, ‘i am another string’, function(err, decoded) {
    if (err) { res.send(500, { error: ‘Failed to authenticate token.’}); }
    else {
      req.user = decoded.user;
      next();
    };
  });
}

app.js

// Routes
app.post(‘/UpdateData’, auth, Routes.UpdateData);
…

如果提供的Token对应于发送请求的用户,我们将继续请求路由。请注意,中间件会修改请求。我们需要引用这个新的user参数,因为我们知道它已经在我们的中间件中设置了。

POST/UpdateData

function UpdateData(req, res) {
  // Only use the user that was set in req by auth middleware!
  var user = req.user;
  updateYourData(user, req.body.data);
  ...
}

我们终于搞定它了! 你的用户已经完全登录,但不需要密码。

UI方面

用户如何在浏览器中实际签署此数据?Metamask会提供帮助!Metamask是一个整洁的chrome扩展,它将web3注入你的浏览器窗口。

mycomponent.jsx

makeSig(dispatch) {

 function toHex(s) {
   var hex = ‘’;
   for(var i=0;i<s.length;i++) { hex += ‘’+s.charCodeAt(i).toString(16); }
   return `0x${hex}`;
 }

 var data = toHex(‘i am a string’);
 web3.currentProvider.sendAsync({ id: 1, method: 'personal_sign', params: [web3.eth.accounts[0], data] },
   function(err, result) {
     let sig = result.result;
     dispatch(exchange.authenticate(sig, user))
    })
  }
}
render(){
  let { dispatch, _main: { sig } } = this.props;
  if (Object.keys(sig).length == 0) { this.makeSig(dispatch); }
  return (
   <p>I am a webpage</p>
  );
}

这将触发Metamask弹出一个窗口,要求用户对消息进行签名:

以太坊和Metamask开发web应用不需要再使用密码-LMLPHP

一旦调用了回调,它将调用以下操作:

authenticate(sig, user) {
  return (dispatch) => {
    fetch(`${this.api}/Authenticate`, {
      method: 'POST',
      body: JSON.stringify({ owner: user, sig: sig}),
      headers: { "Content-Type": "application/json" }
    })
    .then((res) => { return res.text(); })
    .then((body) => {
      var token = JSON.parse(body).token;
      dispatch({ type: 'SET_AUTH_TOKEN', result: token})
    })
  }
}

一旦你在reducer中保存了auth token,你就可以调用经过身份验证的端点。我们终于得到它了!

请注意,必须从签名中恢复vrs值。Metamask有一个签名util模块,用于显示签名的构造方式。它可以像这样解构:

var solidity_sha3 = require('solidity-sha3').default;
let hash = solidity_sha3(data);
let sig = result.result.substr(2, result.result.length);
let r = sig.substr(0, 64);
let s = sig.substr(64, 64);
let v = parseInt(sig.substr(128, 2));

其中r将被解析为0或1.另请注意,这使用solidity-sha3模块来确保此哈希算法与用作solidity本机hash方法的哈希算法相同(我们正在hash之前签名的十六进制字符串))。

生产准备

我无法强调使用JSON Web token的每个Web应用程序今天都可以轻松利用这一点。具有Metamask扩展的任何用户都可以简单地绕过登录屏幕,其安全性可能比目前用于管理登录的任何内容都要好。这意味着更少的忘记密码,更少的浪费时间和更快乐的用户群。

而且,你知道,如果你希望你的用户在没有中间人的情况下向对方(或你或使用此用户的任何其他系统上的用户)付款,或者如果你想要利用以太坊的其他百万其他功能,那么你需要也这样做。

今天开始,加入我们以太坊,去征服世界。

======================================================================

分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:

汇智网原创翻译,转载请标明出处。这里是原文不要再在以太坊和Metamask开发web时使用密码

11-27 22:54