用以太坊區塊鏈保證Asp.Net Core的API安全(下)
上一篇ofollow,noindex" target="_blank">用以太坊區塊鏈保證Asp.Net Core的API安全 我們介紹了基本的解決方案,這一篇我們重點來看客戶端。
正如我們所說,我們的DApp是一個簡單的HTML/ES6客戶端。我們將在Asp.Net Core 2之上構建客戶端,以利用IIS Express和Visual Studio IDE
。因此,Startup.cs
類中的Configure
方法將是:
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseDefaultFiles(); app.UseStaticFiles();
使DApp成為NPM
專案並安裝必備條件以使用ES6 Javascript標準。這不是強制性的,可以使用自己的堆疊構建DApp。
從專案資料夾執行Powershell
並執行以下NPM
命令:
npm init npm install webpack npm install babel-core babel-loader --save-dev npm install babel-preset-es2015 --save-dev npm install babel-preset-stage-0 --save-dev npm install babel-polyfill --save npm install babel-runtime --save npm install babel-plugin-transform-runtime --save-dev
要配置webpack/babel
,請使用以下配置建立webpack.config.js
檔案:
var path = require("path"); module.exports = { entry: [ "babel-polyfill", "./src/main" ], output: { publicPath: "/js/", path: path.join(__dirname, "/wwwroot/js/"), filename: "main.build.js" } };
我們已設定webpack
將src/main.js
檔案構建到/www/js/main.build.js
。
安裝以太坊擴充套件包:
npm install web3 npm install ethereumjs-util
Web3是一個javascript
封裝包,它簡化了針對以太坊區塊鏈的JSON RPC呼叫。Ethereumjs-util
提供了一些以太坊特定的實用程式。讓我們構建一個非常簡單的HTML頁面。我們需要一個登入按鈕和另一個按鈕來從我們的API層載入一些安全資料:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Ethereum Jwt Client</title> </head> <body> <h1>Ethereum Jwt Client</h1> <div id="login-view"> <label>Your account: </label> <span id="eth_account_span"></span> <button type="submit" id="login_btn">Login</button> </div> <div id="data-view"> <button type="submit" id="load_data_btn">Request secured data</button> <ul id="data_list"> </ul> </div> <script src="js/main.build.js"></script> </body> </html>
DApp邏輯將駐留在src/main.js
檔案中,正如我們在webpack.config.js
檔案中指定的那樣。src/main.js
檔案將是:
let ethUtil = require('ethereumjs-util'); let Web3 = require('web3'); let coinbase = null; let accessToken = null; let init = () => { if (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider); web3.eth.getCoinbase(function (err, account) { if (err === null && ethUtil.isValidAddress(account)) { coinbase = account; eth_account_span.innerHTML = coinbase; } else { eth_account_span.innerHTML = 'Please unlock your account and refresh the page'; console.error(err); } }); } else { eth_account_span.innerHTML = 'Please install or unlock Metamask browser plugin or navigate this page with Mist or another web3 browser'; } }; let request = obj => { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open(obj.method || "GET", obj.url); if (obj.headers) { Object.keys(obj.headers).forEach(key => { xhr.setRequestHeader(key, obj.headers[key]); }); } xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response); } else { reject(xhr.statusText); } }; xhr.onerror = () => reject(xhr.statusText); xhr.send(obj.body); }); }; login_btn.addEventListener('click', (e) => { e.preventDefault(); login_btn.setAttribute('disabled', 'disabled'); login_btn.innerHTML = 'Please sign the message'; let plain = 'Hi, you request a login from client to Eth Jwt Api. Please sign this message. This is not a transaction, is completely free and 100% secure. We\'ll use your signature to prove your ownership over your private key server side.'; let msg = ethUtil.bufferToHex(new Buffer(plain, 'utf8')); let hash = ethUtil.bufferToHex(ethUtil.keccak256("\x19Ethereum Signed Message:\n" + plain.length + plain)); let from = coinbase; let params = [msg, from]; let method = 'personal_sign'; web3.currentProvider.sendAsync({ method, params, from, }, function (err, result) { if (err || result.error) { login_btn.removeAttribute('disabled'); login_btn.innerHTML = 'Login'; console.error(err); return console.error(result.error); } console.log({ 'signature': result.result, 'msg': msg, 'hash': hash }); login_btn.innerHTML = 'Requesting token...'; let loginData = {}; loginData.signer = from; loginData.signature = result.result; loginData.message = msg; loginData.hash = hash; request({ url: 'http://localhost:49443/api/token', body: JSON.stringify(loginData), method: 'post', headers: { 'Authorization': 'Bearer ' + accessToken, 'Content-type': 'application/json' } }).then(data => { var json = JSON.parse(data); accessToken = json.token; console.log('access token: ' + accessToken); login_btn.removeAttribute('disabled'); login_btn.innerHTML = 'Login'; }).catch(error => { console.error(error); login_btn.removeAttribute('disabled'); login_btn.innerHTML = 'Login'; }); }); }); load_data_btn.addEventListener('click', (e) => { e.preventDefault(); request({ url: 'http://localhost:49443/api/values', headers: { 'Authorization': 'Bearer ' + accessToken } }).then(data => { var json = JSON.parse(data); for (let i = 0; i < json.length; i++) { data_list.innerHTML += '<li>' + json[i] + '</li>'; } }).catch(error => { console.error(err); }); }); window.addEventListener('load', init);
-
1.
coinbase
和accessToken
是全域性變數,分別儲存使用者以太坊帳戶和JWT token。 -
2.
init
函式從Metamask提供的提供程式初始化web3物件,然後它嘗試檢索使用者的帳戶(coinbase)。這需要解鎖在Metamask中籤名的帳戶。 -
3.
require
函式只是hxr物件的封裝,可以輕鬆地向API層呼叫ajax。 -
4.load_data_btn
單擊處理程式對API層安全端點進行ajax呼叫。這需要有效的
accessToken
才能工作,否則,API層將響應401 HTTP響應。 - 5.login_btn 單擊是一個兩步功能。首先,它要求使用者簽署任意訊息。簽名後,它會將帳戶,簽名,明文訊息和帶字首的雜湊傳送到令牌端點。
請注意,web3.personal.sign
將十六進位制格式(0x …)的普通字串的位元組陣列作為輸入。
正如我們所說的,伺服器端,我們將使用兩種不同的方式從簽名中恢復公鑰:在一箇中我們將使用JSON RPC 介面中的web3.personal.ecrecover
(web3.personal.sign對應);在另一箇中,我們將使用底層的ecrecover
離線功能。根據文件,web3.personal.sign
使用底層簽名函式來簽署hash和字首訊息,因此,為了使用底層ecrecover
對應,我們還需要計算並將此hash傳送到令牌端點。
執行兩個應用程式並使用安裝了Metamask外掛的瀏覽器導航到客戶端。請記住,為了將src/main.js
檔案構建到js/main.build.js
,你需要從Powershell
執行webpac
命令。如果一切正常,客戶端將檢索coinbase
,你將在頁面上看到你的帳戶:
如果你現在單擊“請求資料”按鈕,將獲得HTTP響應401。如果單擊“登入”按鈕,Metamask將提示你簽名:
簽名後,處理程式將對令牌端點進行ajax呼叫。在此階段,身份驗證方法不會檢查任何簽名,因此端點將始終發出JWT令牌。一旦收到JWT令牌,客戶端就能通過ajax呼叫安全端點。如果現在單擊“請求資料”按鈕,將收到HTTP響應200和資料負載:
從簽名中檢索以太坊帳戶
到目前為止,EthereumJwtApi
是一個簡單的JWT Asp.Net核心示例,因為它不提供任何有效的身份驗證方法。
TokenController
的關鍵部分是兩個Authenticate
方法及其從簽名中檢索以太坊帳戶的能力。為此,你需要安裝Nethereum.Web3 NuGet
包。Nethereum是以太坊的.Net實現。
Authenticate
方法只是對web3.personal.ecrecover
函式進行JSON RPC呼叫:
private async Task<UserVM> Authenticate(LoginVM login) { UserVM user = null; var client = new RpcClient(new Uri(_config["Nethereum:Geth"])); // Require the RPC endpoint of a Geth node as input eg: http://127.0.0.1:8545 var signer = await client.SendRequestAsync<string>(new RpcRequest(1, "personal_ecRecover", login.Message, login.Signature)); if (signer.ToLower().Equals(login.Signer.ToLower())) { // read user from DB or create a new one // for now we fake a new user user = new UserVM { Account = signer, Name = string.Empty, Email = string.Empty }; } return user; }
PRO:
web3.personal.sign
是web3.personal.sign
的對應部分,因此你無需擔心其底層實現。
缺點:
需要你自己的Geth節點。不支援Parity,Infura不允許JSON RPC呼叫web3.personal.*
。Authenticate2
方法顯示了另一種方法,它使用底層ecrecover
功能的離線實現:
private async Task<UserVM> Authenticate2(LoginVM login) { UserVM user = null; var signer = new Nethereum.Signer.MessageSigner(); var account = signer.EcRecover(login.Hash.HexToByteArray(), login.Signature); if (account.ToLower().Equals(login.Signer.ToLower())) { // read user from DB or create a new one // for now we fake a new user user = new UserVM { Account = account, Name = string.Empty, Email = string.Empty }; } return user; }
PRO:
不需要JSON RPC呼叫就能工作。MessageSigner.EcRecover
是Nethereum
提供的離線功能。
缺點:
你需要處理web3.personal.sign
實現才能正確恢復帳戶。出於這個原因,在客戶端,我們相應地計算了字首訊息雜湊。
結論
現在你擁有基本的知識和一個專案的骨架,可以使用以太坊保護你的Asp.Net Core 2 API。只需幾點說明:
web3 1.0.0處於測試階段,web3.personal.sign
實現可能會隨著時間的推移而變化。請務必在你可以維護的程式碼庫上使用這種身份驗證方法。也許Infura某天決定允許web3.personal.ecrecover
:-)
======================================================================
分享一些以太坊、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工程師不可多得的比特幣開發學習課程。
匯智網原創翻譯,轉載請標明出處。這裡是原文