範例:實作雙因素驗證

摘要:第八次當選 MVP 好開心實作雙因素驗證的一個簡單範例。

Scott Hanselman 前天寫了一篇有關雙因素驗證(two-factor authentication)的文章:Adding Two-Factor authentication to an ASP.NET application,想到自己最近也在專案中實作了雙因素驗證,而發送驗證碼的部分也恰好跟他文章裡的範例一樣是透過 Twilio 發簡訊至手機。

不過,人家是貨真價實的運用 ASP.NET Identity 2.0,我這裡的範例不過就是使用了 IIdentityMessageService 和 IdentityMessage 這兩個型別,充其量只跟 Identity 2.0 沾點邊而已。用來示範雙因素驗證的基本流程以及如何透過 Twilio 服務平台發送簡訊倒是真的。

以下說明實作此範例的步驟。

開發工具:Visual Studio 2013

Step 1. 建立新專案

目標平台:.NET Framework 4.5
專案名稱:TwoFactorAuthDemo

選擇空白專案範本,勾選 Web Forms:



透過 NuGet Manager 加入組件參考:Microsoft.AspNet.Identity.Core 以及  Twilio REST API:



另外再加入 jQuery 2.x 套件,並於 Global.asax.cs 中撰寫 Application_Start 方法:

protected void Application_Start(object sender, EventArgs e)
{
    ScriptManager.ScriptResourceMapping.AddDefinition(
        "jquery", 
        new ScriptResourceDefinition
        {
            Path = "~/Scripts/jquery-2.1.0.js"
        });
}
註:若少了此步驟,網頁執行時會出現錯誤訊息「UnobtrusiveValidationMode requires a ScriptResourceMapping for 'jquery'. Please add a ScriptResourceMapping named jquery(case-sensitive)」

Step 2:建立登入頁面

New 一個 Web Form,取名為 Login.aspx。從 Toolbox 拖拉一個 Login 控制項至網頁上,然後撰寫該控制項的 Authenticate 事件處理函式:

protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
{
    var authService = new Services.AuthenticationService();
    if (authService.AuthenticateAndSendToken(Login1.UserName, Login1.Password).Result)
    {
        e.Authenticated = true;
        Response.Redirect("CheckAuthToken.aspx");
    }
    else
    {
        e.Authenticated = false;
        Response.Write("帳號密碼驗證失敗!");
    }
}

我把身分驗證的邏輯寫在 Services 資料夾下的 AuthenticationService 類別裡,該類別的 AuthenticateAndSendToken 方法會負責檢查帳號密碼,若帳密輸入正確,則透過簡訊服務發送一組驗證碼到使用者的手機。

Step 3:實作身分驗證與發送驗證碼的邏輯

驗證身分的邏輯是由 AuthenticationService 負責,程式碼如下:

using Microsoft.AspNet.Identity;
using System.Threading.Tasks;

namespace TwoFactorAuthDemo.Services
{
    public class AuthenticationService
    {        
        public async Task<bool> AuthenticateAndSendToken(string userId, string password)
        {
            if (userId == "michael" && password == "1234")
            {
                // 透過 User DAO 取得使用者資訊(目的是取得手機號碼)
                string userPhone = "+88693668XXXX";

                // 產生隨機驗證碼,儲存於資料庫中,並且設定其有效期限(例如五分鐘)
                string authToken = "9999";

                // 準備發送驗證碼的訊息
                var idMsg = new IdentityMessage()
                {
                    Body = "您的登入驗證碼是: " + authToken,
                    Destination = userPhone
                    // 註:若是透過 e-mail 發送驗證碼則還需要設定 Subject 屬性.
                };

                // 發送驗證碼
                var smsService = new SmsService();
                await smsService.SendAsync(idMsg); 

                return true;
            }

            return false;
        }

        public bool CheckToken(string userId, string token)
        {
            // 從資料庫中取出此 userId 的二階段驗證碼,並檢查是否已過期或已經驗證過.
            string authToken = "9999";

            return (token == authToken);
        }
    }
}

此類別包含兩個方法:
  • AuthenticateAndSendToken-檢查帳號密碼,並且發送一組驗證碼至使用者的手機。
  • CheckToken-檢查驗證碼是否正確。這個方法稍後會用到。

其中透過簡訊傳送驗證碼的部分是透過另一個類別來處理:SmsService。

Step 4:發送驗證碼

SmsService 類別負責發送簡訊。我讓它實作 ASP.NET Identity 的 IIDentityMessageService 介面,而該介面只有一個方法:SendAsync。程式碼如下:

using Microsoft.AspNet.Identity;
using System;
using System.Threading.Tasks;
using Twilio;

namespace TwoFactorAuthDemo.Services
{
    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            string twilioAccountSid = "AC060000111122223333444455551af92";
            string twilioAccountToken = "94639-這應該只有你自己知道-f3cd06";
            string fromPhone = "+13478972288";
            string toPhone = message.Destination;

            var twilio = new TwilioRestClient(twilioAccountSid, twilioAccountToken);
            var msg = twilio.SendMessage(fromPhone, toPhone, message.Body);

            if (String.IsNullOrEmpty(msg.Sid) == false)
            {                
                return Task.FromResult(false);
            }

            return Task.FromResult(true);
        }
    }
}

Twilio 是位於美國的一個通訊服務平台,它提供了很方便的程式呼叫介面,讓開發人員能夠以 HTTP GET/POST 的方式發送簡訊(也能接收簡訊)。我事先已經在 Twilio 網站上註冊了個人的試用帳戶,可以發送簡訊到自己的手機。上列程式碼中,指定給 fromPhone 的電話號碼,就是我的 Twilio 帳戶的電話號碼。

Step 5:讓使用者輸入驗證碼

加入一個新的 Web Form,命名為 CheckAuthToken.aspx。在網頁上放一個 TextBox 讓使用者輸入驗證碼,再放一個確認按鈕。撰寫按鈕的 Click 事件處理常式:

protected void btnOk_Click(object sender, EventArgs e)
{
    var svc = new Services.AuthenticationService();
    if (svc.CheckToken("michael", txtAuthCode.Text))
    {
        Response.Write("登入成功!");
    }
    else
    {
        Response.Write("登入失敗! 請重新輸入,或發送新的驗證碼。");
    }
}

這樣就實作完成一個基本的雙因素驗證機制了。

執行結果

在登入頁面輸入帳號密碼:

點擊【登入】按鈕之後,查看手機的簡訊以取得驗證碼:



接著回到輸入驗證碼的頁面:


收工!

延伸閱讀

Post Comments

技術提供:Blogger.