Stories

Detail Return Return

青山不遮,畢竟東流,集成Web3.0身份錢包MetaMask以太坊一鍵登錄(Tornado6+Vue.js3) - Stories Detail

原文轉載自「劉悦的技術博客」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 BaseHandler

from 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

user avatar zs_staria Avatar tanggoahead Avatar Poetwithapistol Avatar laggage Avatar nzbin Avatar yuxl01 Avatar esunr Avatar dirackeeko Avatar potato1314 Avatar ruanjiankaifa_xiaofanya Avatar linong Avatar lpicker Avatar
Favorites 43 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.