不要再在以太坊和Metamask开发web时使用密码

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

JSON Web token

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

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

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

这是使用Express的样子:

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

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

1
2
3
4
5
6
7
8
9
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

1
2
3
// Routes
app.post(‘/UpdateData’, auth, Routes.UpdateData);

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

POST/UpdateData

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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弹出一个窗口,要求用户对消息进行签名:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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模块,用于显示签名的构造方式。它可以像这样解构:

1
2
3
4
5
6
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、比特币等区块链相关的交互式在线编程实战教程:

  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

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