原文转载自「刘悦的技术博客」https://v3u.cn/a_id_213
上世纪九十年代,海湾战争的时候,一位美军军官放心他们的五角大楼会被敌人的一枚导弹干掉,从而导致在寰球的美军基地处于瘫痪状态。这时候,有一位蠢才的科学家说,最好的核心就是没有核心。是的,这就是最奢侈的去中心化思维,于是互联网呈现了。一个没有互联网的时代是无奈设想的,互联网的外围就是把一个信息分成若干的小件,用不同的路径流传进来,怎么不便怎么走。
三十年后的明天,去中心化身份逐步被宽泛采纳。用户的局部在线流动在链上是公开的,可通过加密钱包搜寻到,用户在链上发明、奉献、赚取和领有的货色,都反映了他们的爱好,也逐步积攒成该用户的身份和标识。
当咱们的用户厌倦了传统的电子邮件/明码注册流程时,他们会抉择Google、GitHub等社交登录形式,这种形式尽管节约了用户的工夫,但登录信息也会被第三方平台记录,也就是说咱们用平台账号做了什么,平台都会高深莫测,甚至还会对咱们的行为进行剖析、画像。那么有没有一种登录形式,它的所有信息都只保留在客户端和后端,并不牵扯三方平台受权,最大化的保障用户隐衷呢?Web3.0给咱们提供了一种抉择:MetaMask。
MetaMask
MetaMask是用于与以太坊区块链进行交互的软件加密货币钱包。MetaMask容许用户通过浏览器插件或挪动应用程序拜访其以太坊钱包,而后能够应用这些扩大程序与去中心化应用程序进行交互。当然了,首先须要领有一个MetaMask钱包,进入https://chrome.google.com/web...
装置metamask浏览器插件:
随后点开插件,创立账号,记录明码、钱包地址、以及助记词等信息。
装置好插件之后,咱们就能够利用这个插件和网站利用做交互了。
钱包登录流程
登录逻辑和传统的三方登录还是有差别的,传统三方登录个别是首先跳转三方平台进行受权操作,随后三方平台将code验证码返回给登录平台,登录平台再应用code申请三方平台换取token,再通过token申请用户账号信息,而钱包登录则是先在前端通过Web3.js浏览器插件中保留的私钥对钱包地址进行签名操作,随后将签名和钱包地址发送到后端,后端利用Web3的库用同样的算法进行验签操作,如果验签通过,则将钱包信息存入token,并且返回给前端。
前端签名操作
首先须要下载前端的Web3.0操作库,https://docs.ethers.io/v4/,随后集成到登录页面中:
<script src="{{ static_url("js/ethers-v4.min.js") }}"></script> <script src="{{ static_url("js/axios.js") }}"></script> <script src="{{ static_url("js/vue.js") }}"></script>
这里咱们基于Vue.js配合Axios应用。
接着申明登录激活办法:
sign_w3:function(){ that = this; ethereum.enable().then(function () { this.provider = new ethers.providers.Web3Provider(web3.currentProvider); this.provider.getNetwork().then(function (result) { if (result['chainId'] != 1) { console.log("Switch to Mainnet!") } else { // okay, confirmed we're on mainnet this.provider.listAccounts().then(function (result) { console.log(result); this.accountAddress = result[0]; // figure out the user's Eth address this.provider.getBalance(String(result[0])).then(function (balance) { var myBalance = (balance / ethers.constants.WeiPerEther).toFixed(4); console.log("Your Balance: " + myBalance); }); // get a signer object so we can do things that need signing this.signer = provider.getSigner(); var rightnow = (Date.now()/1000).toFixed(0) var sortanow = rightnow-(rightnow%600) this.signer.signMessage("Signing in to "+document.domain+" at "+sortanow, accountAddress, "test password!") .then((signature) => { that.handleAuth(accountAddress,signature); }); console.log(this.signer); }) } }) }) },
通过应用signMessage办法返回签名,这里加签过程中应用基于工夫戳的随机数避免未签名,以后端签名生成好之后,立即异步申请后盾接口:
//查看验证 handleAuth:function(accountAddress, signature){ this.myaxios("/checkw3/","post",{"public_address":accountAddress,"signature":signature}).then(data =>{ if(data.errcode==0){ alert("欢送:"+data.public_address); localStorage.setItem("token",data.token); localStorage.setItem("email",data.public_address); window.location.href = "/"; }else{ alert("验证失败"); } }); }
这里将以后账户的钱包地址和签名传递给后端,如图所示:
残缺页面代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Edu</title> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"> <link rel="stylesheet" href="{{ static_url("css/min.css") }}" > <link rel="icon" href="/static/img/favicon.68cbf4197b0c.png"> <script src="{{ static_url("js/ethers-v4.min.js") }}"></script> <script src="{{ static_url("js/axios.js") }}"></script> <script src="{{ static_url("js/vue.js") }}"></script> </head> <body> <div> {% include "head.html" %} <div id="app" class="container main-content"> <div class="row justify-content-center"> <div class="col-md-10 col-lg-8 article"> <div class="article-body page-body mx-auto" style="max-width: 400px;"> <h1 class="text-center mb-4">Sign-in</h1> <div class="socialaccount_ballot"> <div class="text-center mb-3"> <ul class="list-unstyled"> <li> <a @click="sign_w3()" title="GitHub" class="socialaccount_provider github btn btn-secondary btn-lg w-100" href="JavaScript:void(0)">Connect With <strong>Meta Mask</strong></a> </li> <li> <a title="GitHub" class="socialaccount_provider github btn btn-secondary btn-lg w-100" href="https://github.com/login/oauth/authorize?client_id=249b69d8f6e63efb2590&redirect_uri=http://localhost:8000/github_back/">Connect With <strong>GitHub</strong></a> </li> </ul> </div> <div class="text-center text-muted my-3">— or —</div> </div> <div class="form-group"> <div id="div_id_login" class="form-group"> <label for="id_login" class=" requiredField"> Email<span class="asteriskField">*</span> </label> <div class=""> <input type="email" v-model="email" placeholder="" autocomplete="email" autofocus="autofocus" class="textinput textInput form-control" > </div> </div> </div> <div class="form-group"> <div id="div_id_password" class="form-group"> <label for="id_password" class=" requiredField"> Password<span class="asteriskField">*</span> </label> <div class=""> <input type="password" v-model="password" placeholder="" autocomplete="current-password" minlength="8" maxlength="99" class="textinput textInput form-control" > </div> </div> </div> <div class="text-center"> <button class="btn btn-primary btn-lg text-wrap px-5 mt-2 w-100" name="jsSubmitButton" @click="sign_on">Sign-In</button> </div> </div> </div> </div> </div> {% include "foot.html" %} </div> <script> const App = { data() { return { email:"", password:"", provider:null, accountAddress:"", signer:null }; }, created: function() { }, methods: { //metamask登录 sign_w3:function(){ that = this; ethereum.enable().then(function () { this.provider = new ethers.providers.Web3Provider(web3.currentProvider); this.provider.getNetwork().then(function (result) { if (result['chainId'] != 1) { console.log("Switch to Mainnet!") } else { // okay, confirmed we're on mainnet this.provider.listAccounts().then(function (result) { console.log(result); this.accountAddress = result[0]; // figure out the user's Eth address this.provider.getBalance(String(result[0])).then(function (balance) { var myBalance = (balance / ethers.constants.WeiPerEther).toFixed(4); console.log("Your Balance: " + myBalance); }); // get a signer object so we can do things that need signing this.signer = provider.getSigner(); var rightnow = (Date.now()/1000).toFixed(0) var sortanow = rightnow-(rightnow%600) this.signer.signMessage("Signing in to "+document.domain+" at "+sortanow, accountAddress, "test password!") .then((signature) => { that.handleAuth(accountAddress,signature); }); console.log(this.signer); }) } }) }) }, //查看验证 handleAuth:function(accountAddress, signature){ this.myaxios("/checkw3/","post",{"public_address":accountAddress,"signature":signature}).then(data =>{ if(data.errcode==0){ alert("欢送:"+data.public_address); localStorage.setItem("token",data.token); localStorage.setItem("email",data.public_address); window.location.href = "/"; }else{ alert("验证失败"); } }); }, sign_on:function(){ if(this.email == ""){ alert("邮箱不能为空"); return false; } if(this.password == ""){ alert("明码不能为空"); return false; } //登录 this.myaxios("/user_signon/","get",{"email":this.email,"password":this.password}).then(data =>{ if(data.errcode != 0){ alert(data.msg); }else{ alert(data.msg); localStorage.setItem("token",data.token); localStorage.setItem("email",data.email); window.location.href = "/"; //localStorage.removeItem("token") } }); }, }, }; const app = Vue.createApp(App); app.config.globalProperties.myaxios = myaxios; app.config.globalProperties.axios = axios; app.config.compilerOptions.delimiters = ['${', '}'] app.mount("#app"); </script> </body> </html>
Tornado后端验签:
有人说,既然钱包私钥是存储在浏览器中,也就是保留在客户端,那签名曾经通过私钥生成了,为什么还要过一遍后端呢?这不是多此一举吗?事实上,攻击者齐全可能获取到前端生成的所有信息,所以签名肯定必须得是后端提供,或者至多有一步后端验证,比方驰名的微信小程序获取openid问题。
后端咱们应用异步框架Tornado,配合web3库进行调用,首先装置依赖:
pip3 install tornado==6.1 pip3 install web3==5.29.1
随后创立异步视图办法:
from tornado.web import url import tornado.web from tornado import httpclient from .base import BaseHandlerfrom web3.auto import w3 from eth_account.messages import defunct_hash_message import time class CheckW3(BaseHandler): async def post(self): public_address = self.get_argument("public_address") signature = self.get_argument("signature") domain = self.request.host if ":" in domain: domain = domain[0:domain.index(":")] now = int(time.time()) sortanow = now-now%600 original_message = 'Signing in to {} at {}'.format(domain,sortanow) print("[+] checking: "+original_message) message_hash = defunct_hash_message(text=original_message) signer = w3.eth.account.recoverHash(message_hash, signature=signature) if signer == public_address: try: user = await self.application.objects.get(User,email=public_address) except Exception as e: user = await self.application.objects.create(User,email=public_address,password=create_password("third"),role=1) myjwt = MyJwt() token = myjwt.encode({"id":user.id}) self.finish({"msg":"ok","errcode":0,"public_address":public_address,"token":token}) else: self.finish({"msg":"could not authenticate signature","errcode":1})
这里通过recoverHash办法对签名进行反编译操作,如果反编译后的钱包地址和前端传过来的钱包地址温和,那么阐明以后账户的身份验证通过:
当验签通过之后,利用钱包地址在后盾创立账号,随后将钱包地址、token等信息返回给前端,前端将其保留在stroage中即可。
结语
没错,将至已至,将来已来,是时候将Web3.0区块链技术融入产品了,尽管有些固有的思维形式仍然在人们的脑海挥之不去,但世界却在时不我待地变动着,正是:青山遮不住,毕竟东流去!我的项目开源在https://github.com/zcxey2911/...\_Vuejs3\_Edu ,与君共觞。
原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_213