用以太坊區塊鏈保證Asp.Net Core的API安全(上)

去中心化應用程序(DApp)的常見設計不僅依賴於以太坊區塊鏈,還依賴於API層。在這種情況下,DApp通過用戶的以太坊帳戶與智能合約進行交互,並通過交換用戶憑據而發佈的JWT token與API層進行交互。

目標是使用以太坊帳戶作爲用戶憑據來請求JWT Token。

最簡單的方法可能是請求用戶使用其他隨機生成的數據在以太坊上進行交易,然後在發出JWT之前檢查交易和隨機數據。這種方法有幾個副作用:

  • 1.用戶必須進行交易並支付gas以進行簡單的身份驗證。
  • 2.用戶必須等待12-120秒(基於耗費的gas)才能完成身份驗證過程。
  • 3.每個用戶的所有登錄操作在以太坊區塊鏈上變得不可公開。

這種方式不實用,並且有一些用戶體驗限制,我們需要一種方法讓用戶證明他擁有與他想要用來登錄的帳戶相關的私鑰,而不是隻(當然)要求私鑰,而不管他是否進行交易。

解決方案

Metamask團隊成員Dan Finlay這篇文章向我啓發了本教程。基本上,你的DApp可以提示用戶使用他的私鑰對短信進行簽名。此簽名操作不會生成交易,並且它由Metamask附加組件透明地處理(順便說一句,你的帳戶需要解鎖)。簽名後,帳戶,消息和簽名將發送到API Token endpoint。驗證方法首先通過接受簽名和明文消息作爲輸入的函數從簽名中推斷帳戶(也稱爲公鑰)。如果計算的以太坊地址等於用戶提供的帳戶,則爲該帳戶發出JWT Token。

請務必注意,整個身份驗證流程不需要用戶名/密碼或OAuth外部服務。用於驗證用戶身份的機制與以太坊用於保證以太坊區塊鏈安全性的機制相同。這要歸功於Go ethereum(Geth)通過Metamask插件提供JSON RPC中的web3.personal.sign

服務器端調用對應的JSON RPC以從簽名中檢索帳戶:web3.personal.ecrecover。在本教程中,我們將構建一個Asp.Net Core 2項目作爲API層,並構建一個簡單的HTML/javascript客戶端作爲DApp,以實際演示此身份驗證過程。

  • 1.從DApp用戶單擊登錄按鈕。這需要Metamask提供的web3對象。
  • 2.Metamask要求用戶通過JSON RPC的web3.personal.sign簽署消息。
  • 3.簽名將發送到API層,該層通過JSON RPC的web3.personal.ecrecover驗證帳戶。
  • 4.驗證後,API層將發佈JWT。

先決條件

  • 1.爲Chrome或Firefox安裝Metamask插件。這個附加組件“將以太坊帶到你的瀏覽器上”。實際上,Metamask提供了一個web3對象,用於與你的DApp中的以太坊區塊鏈進行交互,處理你的私鑰並在瀏覽器中管理交易。
  • 2.可選的。運行Geth節點。我將向你展示兩種從簽名中恢復以太坊帳戶的方法,其中一種方法需要你的API層針對Geth節點調用JSON RPC。注意:Infura現在還不行,因爲它們不允許大多數web3.personal.*的JSON RPC接口。出於開發目的,運行Geth節點非常簡單。在生產環境中,出於安全考慮,運行Geth節點並不是一項簡單的任務。最好的方法是依靠AWS或Azure提供的區塊鏈即服務堆棧(BaaS)。
  • 3.開發堆棧:Visual Studio 2017和節點包管理器(NPM)。
  • 4.以太坊/Asp.Net核心/前端開發的基礎知識,JWT認證流程的基礎知識。

開始

打開Visual Studio 2017,創建EthereumJwtSolution並添加兩個Asp.Net Core 2 Web應用程序項目:EthereumJwtApiEthereumJwtClient。爲兩個項目選擇空項目腳手架。

EthereumJwtClient只是一個HTML/Javascript客戶端。我們將在Asp.Net Core上構建客戶端應用程序,只是爲了在IIS Express上輕鬆運行它。

我們需要準備EthereumJwtApi來創建和處理JWT token,以保護一些安全端點。任務很簡單,因爲Asp.Net Core 2有一個內置的JWT機制,可以插入我們的應用程序。 打開Startup.cs並修改ConfigureServices方法:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Jwt:Issuer"],
            ValidAudience = Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
        };
    });

    services.AddCors(options =>
    {
        options.AddPolicy("CorsPolicy",
            builder => builder.AllowAnyOrigin()
              .AllowAnyMethod()
              .AllowAnyHeader()
              .AllowCredentials()
        .Build());
    });

    services.AddMvc();

然後修改Configure方法:

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

app.UseCors("CorsPolicy");

app.UseAuthentication();

app.UseMvc();

我們告訴我們的API應用程序使用JWT身份驗證服務。爲了與我們的用戶合作,我們還需要配置Cors策略。我們在appsetting.json中定義設置JWT配置:

"Jwt": {
    "Key": "averysecretpassphrase", // A random and secure passhphrase
    "Issuer": "http://localhost:49443/", // This API base URI
    "Audience": "http://localhost:51149/" // The client base URI
  },

爲測試目的創建一個簡單的可能安全端點:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    // GET api/values
    [HttpGet, Authorize]
    public IEnumerable<string> Get()
    {
        return new string[] { "Secret 1", "Secret 2" };
    }
}

TokenController.cs將處理JWT請求和相關的token問題:

[Route("api/[controller]")]
public class TokenController : Controller
{
    private IConfiguration _config;

    public TokenController(IConfiguration config)
    {
        _config = config;
    }

    [AllowAnonymous]
    [HttpPost]
    public async Task<IActionResult> CreateToken([FromBody]LoginVM login)
    {
        var user = await Authenticate(login);

        if (user != null)
        {
            var tokenString = BuildToken(user);
            return Ok(new { token = tokenString });
        }

        return Unauthorized();
    }

    private string BuildToken(UserVM user)
    {
        var claims = new[] {
            new Claim(JwtRegisteredClaimNames.Sub, user.Account),
            new Claim(JwtRegisteredClaimNames.GivenName, user.Name),
            new Claim(JwtRegisteredClaimNames.Email, user.Email),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(_config["Jwt:Issuer"],
          _config["Jwt:Audience"],
          claims,
          expires: DateTime.Now.AddMinutes(30),
          signingCredentials: creds);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    private async Task<UserVM> Authenticate(LoginVM login)
    {
        // TODO: this method will authenticate the user recovering the Ethereum address from signature using the Geth RPC web3.personal.ecrecover API

        UserVM user = user = new UserVM { Account = login.Account, Name = string.Empty, Email = string.Empty };

        return user;
    }

    private async Task<UserVM> Authenticate2(LoginVM login)
    {
        // TODO: This method will authenticate the user recovering his Ethereum address through underlaying offline ecrecover method.
        
        UserVM user = user = new UserVM { Account = login.Account, Name = string.Empty, Email = string.Empty };

        return user;
    }

這是一個典型的JWT控制器,核心方法,AuthenticateAuthenticate2尚未實現。一旦實現,他們將完成相同的工作:從簽名中恢復以太坊地址,並檢查它是否等於客戶端提供的以太坊地址。

LoginVM表示客戶端提供的用戶憑據,UserVM表示“服務器端”登錄用戶:

public class LoginVM
{
    public string Signer { get; set; } // Ethereum account that claim the signature
    public string Signature { get; set; } // The signature
    public string Message { get; set; } // The plain message
    public string Hash { get; set; } // The prefixed and sha3 hashed message 
}

public class UserVM
{
    public string Account { get; set; } // Unique account name (the Ethereum account)
    public string Name { get; set; } // The user name
    public string Email { get; set; } // The user Email
}

Authenticate方法將SignatureMessage屬性作爲ecRecover函數的輸入,Authenticate2方法將採用SignatureHash屬性。我稍後會解釋其中的差異。

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

分享一些以太坊、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工程師不可多得的比特幣開發學習課程。

原文在這裏以太坊 區塊鏈 Asp.Net Core API

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章