以太坊區(qū)塊鏈如何保證Asp.Net Core的API安全(下)

上一篇用以太坊區(qū)塊鏈保證Asp.Net Core的API安全(上)我們介紹了基本的解決方案宝穗,這一篇我們重點來看客戶端猴伶。

正如我們所說邀跃,我們的DApp是一個簡單的HTML/ES6客戶端坛猪。我們將在Asp.Net Core 2之上構(gòu)建客戶端涩搓,以利用IIS Express和Visual Studio IDE污秆。因此,Startup.cs類中的Configure方法將是:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.UseDefaultFiles();
app.UseStaticFiles();

使DApp成為NPM項目并安裝必備條件以使用ES6 Javascript標準昧甘。這不是強制性的混狠,可以使用自己的堆棧構(gòu)建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疾层,請使用以下配置創(chuàng)建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"
    }
};

我們已設(shè)定webpacksrc/main.js文件構(gòu)建到/www/js/main.build.js将饺。

安裝以太坊擴展包:

npm install web3
npm install ethereumjs-util

Web3是一個javascript封裝包,它簡化了針對以太坊區(qū)塊鏈的JSON RPC調(diào)用痛黎。Ethereumjs-util提供了一些以太坊特定的實用程序予弧。讓我們構(gòu)建一個非常簡單的HTML頁面。我們需要一個登錄按鈕和另一個按鈕來從我們的API層加載一些安全數(shù)據(jù):

<!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.coinbaseaccessToken是全局變量,分別存儲用戶以太坊帳戶和JWT token井厌。
  • 2.init函數(shù)從Metamask提供的提供程序初始化web3對象蚓庭,然后它嘗試檢索用戶的帳戶(coinbase)。這需要解鎖在Metamask中簽名的帳戶仅仆。
  • 3.require函數(shù)只是hxr對象的封裝器赞,可以輕松地向API層調(diào)用ajax。
  • 4.load_data_btn單擊處理程序?qū)PI層安全端點進行ajax調(diào)用墓拜。這需要有效的accessToken才能工作港柜,否則,API層將響應(yīng)401 HTTP響應(yīng)。
  • 5.login_btn單擊是一個兩步功能夏醉。首先爽锥,它要求用戶簽署任意消息。簽名后畔柔,它會將帳戶氯夷,簽名,明文消息和帶前綴的哈希發(fā)送到令牌端點靶擦。

請注意肠槽,web3.personal.sign將十六進制格式(0x ...)的普通字符串的字節(jié)數(shù)組作為輸入。

正如我們所說的奢啥,服務(wù)器端秸仙,我們將使用兩種不同的方式從簽名中恢復(fù)公鑰:在一個中我們將使用JSON RPC 接口中的web3.personal.ecrecover(web3.personal.sign對應(yīng));在另一個中,我們將使用底層的ecrecover離線功能桩盲。根據(jù)文檔寂纪,web3.personal.sign使用底層簽名函數(shù)來簽署hash和前綴消息,因此赌结,為了使用底層ecrecover對應(yīng)捞蛋,我們還需要計算并將此hash發(fā)送到令牌端點。

運行兩個應(yīng)用程序并使用安裝了Metamask插件的瀏覽器導(dǎo)航到客戶端柬姚。請記住拟杉,為了將src/main.js文件構(gòu)建到js/main.build.js,你需要從Powershell運行webpac命令量承。如果一切正常搬设,客戶端將檢索coinbase,你將在頁面上看到你的帳戶:

image

如果你現(xiàn)在單擊“請求數(shù)據(jù)”按鈕撕捍,將獲得HTTP響應(yīng)401拿穴。如果單擊“登錄”按鈕,Metamask將提示你簽名:

image

簽名后忧风,處理程序?qū)α钆贫它c進行ajax調(diào)用默色。在此階段,身份驗證方法不會檢查任何簽名狮腿,因此端點將始終發(fā)出JWT令牌腿宰。一旦收到JWT令牌,客戶端就能通過ajax調(diào)用安全端點缘厢。如果現(xiàn)在單擊“請求數(shù)據(jù)”按鈕吃度,將收到HTTP響應(yīng)200和數(shù)據(jù)負載:

image

從簽名中檢索以太坊帳戶

到目前為止,EthereumJwtApi是一個簡單的JWT Asp.Net核心示例昧绣,因為它不提供任何有效的身份驗證方法规肴。

TokenController的關(guān)鍵部分是兩個Authenticate方法及其從簽名中檢索以太坊帳戶的能力捶闸。為此夜畴,你需要安裝Nethereum.Web3 NuGet包拖刃。Nethereum是以太坊的.Net實現(xiàn)。

Authenticate方法只是對web3.personal.ecrecover函數(shù)進行JSON RPC調(diào)用:

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.signweb3.personal.sign的對應(yīng)部分贪绘,因此你無需擔(dān)心其底層實現(xiàn)兑牡。

缺點:

需要你自己的Geth節(jié)點。不支持Parity税灌,Infura不允許JSON RPC調(diào)用web3.personal.*均函。Authenticate2方法顯示了另一種方法,它使用底層ecrecover功能的離線實現(xiàn):

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調(diào)用就能工作菱涤。MessageSigner.EcRecoverNethereum提供的離線功能苞也。

缺點:

你需要處理web3.personal.sign實現(xiàn)才能正確恢復(fù)帳戶。出于這個原因粘秆,在客戶端如迟,我們相應(yīng)地計算了前綴消息哈希。

結(jié)論

現(xiàn)在你擁有基本的知識和一個項目的骨架攻走,可以使用以太坊保護你的Asp.Net Core 2 API殷勘。只需幾點說明:

web3 1.0.0處于測試階段,web3.personal.sign實現(xiàn)可能會隨著時間的推移而變化昔搂。請務(wù)必在你可以維護的代碼庫上使用這種身份驗證方法玲销。也許Infura某天決定允許web3.personal.ecrecover :-)

======================================================================

分享一些以太坊、EOS摘符、比特幣等區(qū)塊鏈相關(guān)的交互式在線編程實戰(zhàn)教程:

  • java以太坊開發(fā)教程贤斜,主要是針對java和android程序員進行區(qū)塊鏈以太坊開發(fā)的web3j詳解。
  • python以太坊逛裤,主要是針對python工程師使用web3.py進行區(qū)塊鏈以太坊開發(fā)的詳解蠢古。
  • php以太坊,主要是介紹使用php進行智能合約開發(fā)交互别凹,進行賬號創(chuàng)建草讶、交易、轉(zhuǎn)賬炉菲、代幣開發(fā)以及過濾器和交易等內(nèi)容堕战。
  • 以太坊入門教程,主要介紹智能合約與dapp應(yīng)用開發(fā)拍霜,適合入門嘱丢。
  • 以太坊開發(fā)進階教程,主要是介紹使用node.js祠饺、mongodb越驻、區(qū)塊鏈、ipfs實現(xiàn)去中心化電商DApp實戰(zhàn),適合進階缀旁。
  • C#以太坊记劈,主要講解如何使用C#開發(fā)基于.Net的以太坊應(yīng)用,包括賬戶管理并巍、狀態(tài)與交易目木、智能合約開發(fā)與交互、過濾器和交易等懊渡。
  • EOS教程刽射,本課程幫助你快速入門EOS區(qū)塊鏈去中心化應(yīng)用的開發(fā),內(nèi)容涵蓋EOS工具鏈剃执、賬戶與錢包誓禁、發(fā)行代幣、智能合約開發(fā)與部署肾档、使用代碼與智能合約交互等核心知識點伴网,最后綜合運用各知識點完成一個便簽DApp的開發(fā)覆糟。
  • java比特幣開發(fā)教程,本課程面向初學(xué)者,內(nèi)容即涵蓋比特幣的核心概念盒发,例如區(qū)塊鏈存儲寿酌、去中心化共識機制根灯、密鑰與腳本哭当、交易與UTXO等,同時也詳細講解如何在Java代碼中集成比特幣支持功能配阵,例如創(chuàng)建地址馏颂、管理錢包、構(gòu)造裸交易等棋傍,是Java工程師不可多得的比特幣開發(fā)學(xué)習(xí)課程救拉。
  • php比特幣開發(fā)教程,本課程面向初學(xué)者瘫拣,內(nèi)容即涵蓋比特幣的核心概念亿絮,例如區(qū)塊鏈存儲、去中心化共識機制麸拄、密鑰與腳本派昧、交易與UTXO等,同時也詳細講解如何在Php代碼中集成比特幣支持功能拢切,例如創(chuàng)建地址蒂萎、管理錢包、構(gòu)造裸交易等淮椰,是Php工程師不可多得的比特幣開發(fā)學(xué)習(xí)課程五慈。

匯智網(wǎng)原創(chuàng)翻譯纳寂,轉(zhuǎn)載請標明出處。這里是原文以太坊區(qū)塊鏈.Net

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泻拦,一起剝皮案震驚了整個濱河市毙芜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聪轿,老刑警劉巖爷肝,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猾浦,死亡現(xiàn)場離奇詭異陆错,居然都是意外死亡,警方通過查閱死者的電腦和手機金赦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門音瓷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人夹抗,你說我怎么就攤上這事绳慎。” “怎么了漠烧?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵杏愤,是天一觀的道長。 經(jīng)常有香客問我已脓,道長珊楼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任度液,我火速辦了婚禮厕宗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘堕担。我一直安慰自己已慢,他們只是感情好,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布霹购。 她就那樣靜靜地躺著佑惠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪齐疙。 梳的紋絲不亂的頭發(fā)上兢仰,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機與錄音剂碴,去河邊找鬼把将。 笑死,一個胖子當著我的面吹牛忆矛,可吹牛的內(nèi)容都是我干的察蹲。 我是一名探鬼主播请垛,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洽议!你這毒婦竟也來了宗收?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤亚兄,失蹤者是張志新(化名)和其女友劉穎混稽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體审胚,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡匈勋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了膳叨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洽洁。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖菲嘴,靈堂內(nèi)的尸體忽然破棺而出饿自,到底是詐尸還是另有隱情,我是刑警寧澤龄坪,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布昭雌,位于F島的核電站,受9級特大地震影響健田,放射性物質(zhì)發(fā)生泄漏烛卧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一抄课、第九天 我趴在偏房一處隱蔽的房頂上張望唱星。 院中可真熱鬧,春花似錦跟磨、人聲如沸间聊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哎榴。三九已至,卻和暖如春僵蛛,著一層夾襖步出監(jiān)牢的瞬間尚蝌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工充尉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留飘言,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓驼侠,卻偏偏與公主長得像姿鸿,于是被迫代替她去往敵國和親谆吴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容