预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
前言
我们将使用以太坊来处理身份验证的问题。我们在这里所给出的是与区块链身份验证技术相关的典型问题。
比特币自2009 年问世以来,以其去中心化的、安全的货币交易理念惊艳了整个世界。以太坊的概念不仅仅只是数字化货币,它通过与图灵完备的智能合约相结合赋能了去中心化的交易模式。在这篇文章,我们联手Ivo Zieliński,Konrad Kozioł,David Belinchon,和来自GFT创新团队的Nicolás González为以太坊用户开发了一套可实现的基于以太坊的登录系统。这是一个理想的登录系统,它能够允许任何以太坊用户在不必使用其私钥的情况下,证明自己的以太坊帐户的所有权,就像他们登录到Facebook一样。
介绍
我们先来看看用户是如何登录到一个简单的系统上的:
1. 一个用户浏览到一个需要登录的第三方网站。该网站要求用户在文本区域内输入以太坊地址。
2. 用户输入其以太坊地址,并单击"登录"。
3. 后端的第三方生成一个挑战字符串,并且签发一个JWT(Json web token),该挑战字符串被嵌入其中。
4. 由于login合约已在以太坊上可用了,用户可以直接将挑战字符串发送给login的方法。
5. 后端持续观察以太坊网络,是否出现由以太坊地址所有者在步骤2中所输入的并且被发送过来的挑战字符串。
6. 如果在合理的时间内,后端监测到挑战字符串,则用来自步骤2的以太坊地址作为标识,将该用户标记为登录成功的状态。一个新的带有完整地址的JWT随即会被发布给第三方网站。
不过上述这种方法存在着一系列的问题:
1. 用户必须选择使用其以太坊的钱包,手动调用login合约里的login方法。
2. 用户必须事先知道login合约的地址和接口。
3. 用户必须花费一些以太币用以登录,因为合约所依赖的events被记录到了区块链上(也就是说,它们执行了写操作)。这使得合约需要消费gas来运行。
4. 在登录完成之前,后端必须等待一个新的块来被开采,并从网络上传播过来(最小的延迟大约是12秒或更长的时间)。
正如你所能想象到的,这些限制使得简单的身份验证都无法被实现。所以我们需要进行改进。
以太坊用户登录系统
我们的系统将依赖于三个关键的要素: 一台身份验证服务器、一个移动应用程序和以太坊的网络。下图是他们之间的交互过程。
要确保用户的以太坊地址与身份验证过程相独立,系统会生成一个完全不同的、仅作身份验证的以太坊地址。该地址通过使用以太坊合约与用户的以太坊地址相关联。换句话说,用户的以太坊地址和系统仅作登录用的地址之间的映射关系会被建立。这种映射在合约的帮助下被存储在以太坊的区块链内。
pragma solidity ^0.4.2;
contract Mapper {
event AddressMapped(address primary, address secondary);
event Error(uint code, address sender);
mapping (address => address) public primaryToSecondary;
mapping (address => bool) public secondaryInUse;
modifier secondaryAddressMustBeUnique(address secondary) {
if(secondaryInUse[secondary]) {
Error(1, msg.sender);
throw;
}
_;
}
function mapAddress(address secondary)
secondaryAddressMustBeUnique(secondary) {
// If there is no mapping, this does nothing
secondaryInUse[primaryToSecondary[msg.sender]] = false;
primaryToSecondary[msg.sender] = secondary;
secondaryInUse[secondary] = true;
AddressMapped(msg.sender, secondary);
}
}
虽然该合约是我们目前所见到最为复杂的,但它仍然是比较容易理解的。让我们一起来解析一下吧:
这里有两个事件: AddressMapped和Error 。 AddressMapped事件是在用户的主以太坊地址映射到一个辅助的、只作登录用的地址时所生成的。Error事件是遇到诸如映射使用到了现有的、已经存在的辅助地址之类的错误时才生成的。
然后是两个变量的声明:primaryToSecondary和secondaryInUse。primaryToSecondary是一个地址的映射:根据主地址,它可以告知其所映射的辅助地址。secondaryInUse是一个指向布尔值的地址的映射,用于检查辅助地址是否已被使用。
接下来的是secondaryAddressMustBeUnique。这个特殊函数是一个修改器。Solidity里的修改器是一些可以附加到合约方法里的特殊函数。这些都是在方法代码之前运行的,并且可以用来修改它们的行为。在本例中,secondaryAddressMustBeUnique使用secondaryInUse变量来确认作为参数传递的辅助地址是否正在使用中。如果是的话,它被标记为错误,而且Error事件就被激发了。如果它不是在使用中,则继续执行。_这个占位符是这个被修改的函数代码在逻辑上插入的位置。
最后一个是mapAddress方法。此方法获得一个辅助的地址,并将它映射到该方法的发方或者是调用方的地址上。以太坊在语义上确保发方是其所声明的身份。换句话说,对于一个地址而言,只有密钥的所有者才能作为发方对Solidity的方法进行调用。这确保了在没有任何特殊检查的情况下,只有合法的主地址所有者才能够建立它与用于登录的辅助地址之间的映射关系。这正是我们系统的关键所在。
总之,我们的合约做到了四点:
它建立了两个以太坊地址之间的映射:一个高值的地址(主地址)和一个低值的、只作登录的辅助地址。
它保证了只有主地址的所有者才可以建立这种映射。
它在区块链中公开地记录了此信息。
它向监控发送了事件,并且对存储在它里面的数据变化作出了反应。
我们的系统就是这样按我们的需要进行工作的。下面让我们来看看注册和身份验证流程是如何一起运作的。我们在此假设用户是具有一定数量以太币、以及具有以太坊帐户的合法拥有者。
注册
这只是在用户初次使用该系统时,所执行的一个一次性的步骤。一旦注册完成,用户可以将其以太坊的地址使用到任何第三方的网站上。换句话说,这是一个全系统的、一次性的步骤。
为了简化身份验证的过程,我们使用一个移动应用程序来接受或拒绝身份验证的请求。用户通过启用其以太坊的帐户,来作为身份验证的一个因素,实现对移动应用程序的首次注册。
注册按照以下的步骤执行:
1. 用户打开移动应用程序。
2. 用户输入其电子邮件地址和屏幕解锁图案。
3. 移动应用程序在后台生成新的以太坊的地址。这是个辅助地址。该地址被便捷地发送到用户的电子邮箱。
4. 用户在其主以太坊地址和辅助地址之间建立链接。要实现这一点,用户可以手动调用Mapper合约的mapAddress方法,或是用到为此目的开发的、特殊的钱包应用程序。这一步需要用户花费其主帐户里最少数量的gas。
5. 一旦地址之间的链接被建立,移动应用程序将显示一个确认的对话框。如果用户确认之,该映射就建立好了,而本过程也就完成。
这种方法的增值好处之一在于:它使得一次性的传销帐户难以使用之。上述步骤4强制用户去花费以太币,来建立其个人的以太坊地址和仅登录用的地址之间的映射。对于这种方式,第三方则可以确保用户所使用的以太坊帐户并非一次性传销的帐户(例如垃圾邮件类帐户)。
身份验证
一旦已注册的用户想要使用其以太坊帐户登录到一个第三方的网站,他或她就必须遵循如下的过程:
1. 用户在输入字段中输入他或她的主以太坊地址或是他或她的电子邮件地址,单击"登录"。
2. 第三方网站联系身份验证服务器,请求验证该地址。要实现这一步,第三方网站需要生成具有特定格式的挑战字符串,并将它传递给身份验证服务器。
3. 身份验证服务器检查该用户在以太坊网络中的当前辅助地址。然后,它在内部数据库中检查必需的数据,以联系与该地址相关联的移动设备。
4. 用户接收移动设备推送的通知,以是接受或拒绝登录请求。
5. 如果用户接受,辅助地址的私有密钥将被用于签发挑战字符串。签发的挑战然后被送回到身份验证服务器上。
6. 身份验证服务器验证该签名,如果其有效且与挑战相匹配,则认为该登录成功。随后,它发回已签名的挑战给第三方网站,作为可选的独立性确认依据。
这就是全部!该方案将签名过程从敏感的主地址中分离出来,在防止暴露潜在重要的私有密钥的同时,仍然使第三方网站能够确认用户是否为该地址的合法所有者。此外,虽然为了方便起见,它依赖到了身份验证服务器,但是它在没有验证服务器的时候也能工作,它可以不把信任这一块放在验证服务器上(第三方网站可以自己检查签名)。因此在最坏的情况下(如身份验证服务器宕机),它也能够保持去中心化,而在平时去能保持便捷性。
缺点
为了方便起见,该方法依赖于身份验证服务器。虽然在没有身份验证服务器时,该系统仍然可能运行,但使用这种方式并不太方便。然而,我们在设计上必须考虑到“如果便捷的、去中心化的操作”是一种强制性的情况。在每种情况下,不信任的因素就被放在了该服务器之中。
试试看!
由于这只是一个概念上的证明,而你在真正初次试水以太坊时可能会有点困难,因此我们在此为新用户给出一个步进式的指南来测试该系统。请注意,这只是一个测试系统,因此它使用的是以太坊的测试网络。换句话说,存储在以太坊的测试网络中的数据完整性并不能得以保障,因此,不要把重要的东西存放在本指南所创建的帐户中,它们是不会受到像以太坊主网络那样同等保障的。
获取以太坊钱包
为了执行以太坊的各种操作,你需要一个钱包。钱包是一个应用程序,它允许你与网络的其余部分进行交互。钱包为你的以太坊地址存储私有密钥。为简单起见,我们将用到Metamask。Metamask是一个基于浏览器的钱包,它作为Chrome的扩展运行在本地。各个密钥被存储在本地,并由它们来签发各笔交易。然后,这些被Metamask所运营的公共节点发送到网络中的其余部分。
1. 获得Metamask
浏览Chrome Webstore(https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn)并安装Metamask。
2. 创建一个新帐户
在你的Chrome窗口的右上角单击Metamask图标,并根据其向导来创建一个帐户。请确保它是在Rinkeby 的测试网络中被创建。要检查这一步是否成功,你可在创建帐户之后,在Metamask窗口的左上角,单击Metamask狐狸旁边的图标。如果你使用的是另外一个网络,请切换到 Rinkeby,然后再跟着向导走下去。
3. 获得一些以太币
为了注册,你将需要用最少数量的以太币。幸运的是,在测试网络中,这是很容易获得的(而在主网络中,你或是需要去购买,或是足够幸运地能够挖到“矿”)。在测试网络中,你可能会使用到"水龙头"(faucets),水龙头是一些得到免费以太币的地方。最普通的Rinkeby水龙头(https://www.rinkeby.io/)是要求用户创建GitHub gist(https://gist.github.com/)。这是简单的限制水龙头被滥用的方法。创建gist是非常容易的,你只需要有一个GitHub的帐户。请创建一个公共的GitHub gist,并将你的Metamask Rinkeby地址粘贴到其中。然后回到水龙头,并将链接放到gist的必填字段处,随后单击"给我以太币"(水龙头位于crypto faucet部分的左边条上)。
经过这一番操作之后,你应该能在Metamask中可以看到你新获得的以太币了。
要获得你的Rinkeby以太坊地址,请转到Metamask,然后单击你的帐户名称旁边的"复制"图标。这将是你主以太坊的地址。在实际的生产系统中,会有大量的以太币出现在该帐户的地址中。这个地址也是在你每次使用以太坊地址登录到第三方网站的时候,所不想暴露的。
获取移动身份验证的应用程序
现在该设置你的辅助地址和登录帮助程序了。此应用程序将被作为身份验证的因子,用来确认你的登录请求。当你想要登录一些网站的时候,你将收到一个来自该应用程序的通知。此通知将允许你接受或拒绝身份验证的请求。
1. 获取应用程序
进入Android Play商店,下载我们的Auth0 PoC应用程序(https://play.google.com/store/apps/details?id=block.chain.auth.zero)。
2. 注册
打开该应用程序并输入你的电子邮件地址。然后选择屏幕解锁图案。当你想登录到网站时,你会被要求输入此相同的图案。然后单击Register。你将被要求在移动应用程序上单击Sign来予以确认。
移动应用程序现在就设置好了,让我们来为登录启用你的以太坊帐户吧。
为登录启用你的以太坊地址
这一步,和以前一样只需执行一次,完成你的主地址和登录地址之间的映射设置。换句话说,它将把你的Metamask帐户连接到你智能手机的移动应用程序上。
1. 获得你的移动应用程序(辅助)地址
如果你现在查看你的电子邮件(请注意检查那些垃圾、促销类邮件),你就会发现你以太坊的辅地址了。这就是通过你的智能手机来管理的帐户地址。请将它复制到剪贴板中。
2. 调用合约 !
如果你是以太坊用户,且有你自己的钱包,你完全可以手动执行此步骤。然而为简单起见,我们已经建立了网站,为你分担了那些繁复的工作。同样使用你已安装了Metamask的Chrome实例,导航到我们PoC钱包(http://auth0-ethereum.com:3002/wallet/)。这个网站是一个简单本地的、钱包类型的应用程序,它创建了调用合约所必须的以太坊事务。该网站与Metamask交互,因此你不必手动输入你的帐户详细信息。
一旦你进入该网站,请将你从上一步电子邮件里复制的以太坊地址粘贴进来,单击Register,会弹出一个Metamask的窗口。这是一个对于你将使用主帐户里的以太币来进行一笔交易的确认,因此请单击Sign。过一会儿后,你主账户和辅助帐户就连接上了,其花费的时间完全取决于以太坊的网络状态。一般也就是几秒钟而已。
考虑你可能已经对以太坊很熟悉了,而且想要自己手动执行此步骤。那么请调用Mapper合约里位于 0x5e24bf433aee99227737663c0a387f02a9ed4b8a的mapAddress方法吧。你也可以通过链接:https://github.com/auth0/ethereum-auth-client/blob/master/config/abi.json来获取JSON API。其唯一的参数就是你在电子邮件里获得的地址。至此,一切都以完成!
登录到我们的测试网站
你现在可以运用你的电子邮件地址或是你的主以太坊地址作为凭据,登录到支持该身份验证方法的任何第三方网站了。请进入我们的示例网站:https://auth0-ethereum.com/authzero,填写你的电子邮件地址,然后单击Login。同时注意查看你的智能手机所弹出的通知,以批准你的登录信息。
你会注意到一个标注着Trustless Authentication的复选框。如前文所述,第三方可能选用不同的安全级别。当被告知登录为有效(信任类型的身份验证)时,它们可以选择信任身份验证服务器;或者它们可能会选择不信任身份验证服务器,而是自己内部去验证签名。在这种情况下,第三方网站必须自己去验证辅助地址的签名,它们首先使用Mapper合约(这是公开可以获得的)来查询辅助地址,然后通过辅助地址的返回数据来验证签名,以查找到辅助地址所对应的公共密钥。这提供的是最高的安全级别,而只是用到身份验证服务器来传递消息。
如果你有兴趣仔细了解我们的PoC是如何工作的话,这里是所有的资源库:
身份验证服务器:https://github.com/auth0/ethereum-authentication-server
移动应用程序:https://github.com/auth0/ethereum-authenticator-app-public
第三方web应用程序的示例:https://github.com/auth0/ethereum-sample-web
使用Metamask来注册钱包:https://github.com/auth0/ethereum-browser-wallet
简单测试的各种docker脚本:https://github.com/auth0/ethereum-docker-deployment
上述的资料库也用到了一些针对此PoC开发的帮助库:
以太坊加密的帮助库:https://github.com/auth0/ethereum-crypto
此PoC用到的、进行身份验证所需的JavaScript库:https://github.com/auth0/ethereum-user-db-service
简单的数据库抽象帮助:https://github.com/auth0/ethereum-user-db-service
为此PoC预配置的以太坊客户端节点:https://github.com/auth0/go-ethereum