2024年11月23日 星期六

啟用 2FA ,使用 Oauth2 及 EF6 進行驗證設置

 

功能需求

  1. OAuth2 驗證
    使用 Bearer Token 作為身份驗證方式。

  2. 雙因素驗證 (2FA)
    用戶啟用 2FA 後,必須輸入 OTP(一次性密碼)以完成身份驗證。

  3. 數據存儲
    使用 EF6 操作用戶及其 2FA 狀態。


1. 準備環境

  1. 建立 ASP.NET Web API 專案

    • 選擇 .NET Framework 的 Web API 模板。
  2. 安裝 NuGet 套件

    • EntityFramework:數據庫 ORM。
    • Microsoft.Owin.Security.OAuth:支持 OAuth2 驗證。
    • OtpNet:生成和驗證 OTP。

2. 資料結構

用戶模型

csharp
using System.ComponentModel.DataAnnotations; public class User { [Key] public string UserId { get; set; } // 用戶唯一 ID [Required] public string PasswordHash { get; set; } // 密碼哈希 public string SecretKey { get; set; } // 2FA 密鑰 public bool TwoFactorEnabled { get; set; } // 是否啟用 2FA }

DbContext 類

csharp
using System.Data.Entity; public class TwoFactorDbContext : DbContext { public TwoFactorDbContext() : base("name=TwoFactorDb") // Web.config 中的連接字符串 { } public DbSet<User> Users { get; set; } // 用戶表 }

3. 配置 OAuth2 Bearer Token

Startup.cs

csharp
using Microsoft.Owin; using Microsoft.Owin.Security.OAuth; using Owin; using System; [assembly: OwinStartup(typeof(YourNamespace.Startup))] public class Startup { public void Configuration(IAppBuilder app) { var OAuthOptions = new OAuthAuthorizationServerOptions { AllowInsecureHttp = true, // 開發環境允許 HTTP(生產環境應使用 HTTPS) TokenEndpointPath = new PathString("/token"), // Token 發行端點 AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30), Provider = new OAuthProvider() // 自定義驗證邏輯 }; app.UseOAuthAuthorizationServer(OAuthOptions); app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); } }

OAuthProvider

csharp
using Microsoft.Owin.Security; using Microsoft.Owin.Security.OAuth; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; public class OAuthProvider : OAuthAuthorizationServerProvider { public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { context.Validated(); // 不進行客戶端驗證 } public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { using (var dbContext = new TwoFactorDbContext()) { var user = dbContext.Users.SingleOrDefault(u => u.UserId == context.UserName); if (user == null || !VerifyPassword(user.PasswordHash, context.Password)) { context.SetError("invalid_grant", "The username or password is incorrect."); return; } // 檢查是否啟用了 2FA if (user.TwoFactorEnabled) { context.SetError("2fa_required", "Two-factor authentication is required."); context.Response.Headers.Add("X-2FA", new[] { "true" }); // 標記需要 2FA return; } var identity = new ClaimsIdentity(context.Options.AuthenticationType); identity.AddClaim(new Claim(ClaimTypes.Name, user.UserId)); context.Validated(new AuthenticationTicket(identity, new AuthenticationProperties())); } } private bool VerifyPassword(string storedPasswordHash, string inputPassword) { // 替換為你的密碼哈希驗證邏輯 return storedPasswordHash == inputPassword; // 示例,請勿直接使用明文驗證 } }

4. 2FA 流程

生成和驗證 OTP

使用 OtpNet 生成和驗證 OTP:

csharp
using OtpNet; public class TwoFactorService { // 生成隨機密鑰 public string GenerateSecretKey() { var key = KeyGeneration.GenerateRandomKey(20); return Base32Encoding.ToString(key); // 編碼為 Base32 格式 } // 生成 OTP public string GenerateOtp(string secretKey) { var totp = new Totp(Base32Encoding.ToBytes(secretKey)); return totp.ComputeTotp(); // 基於時間生成 OTP } // 驗證 OTP public bool ValidateOtp(string secretKey, string userInput) { var totp = new Totp(Base32Encoding.ToBytes(secretKey)); return totp.VerifyTotp(userInput, out _); // 驗證用戶輸入 } }

2FA API

用於驗證 OTP 並發放完整的 Bearer Token:

csharp
using System.Linq; using System.Web.Http; [RoutePrefix("api/twofactor")] public class TwoFactorController : ApiController { private readonly TwoFactorService _twoFactorService = new TwoFactorService(); [HttpPost] [Route("validate")] public IHttpActionResult ValidateTwoFactor([FromBody] TwoFactorRequest request) { using (var dbContext = new TwoFactorDbContext()) { var user = dbContext.Users.SingleOrDefault(u => u.UserId == request.UserId); if (user == null) { return BadRequest("User not found."); } if (!_twoFactorService.ValidateOtp(user.SecretKey, request.Otp)) { return BadRequest("Invalid OTP."); } // 發放完整的 Bearer Token var identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType); identity.AddClaim(new Claim(ClaimTypes.Name, user.UserId)); var ticket = new AuthenticationTicket(identity, new AuthenticationProperties()); var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket); return Ok(new { AccessToken = accessToken }); } } } public class TwoFactorRequest { public string UserId { get; set; } public string Otp { get; set; } }

5. API 流程

  1. 獲取初步 Token:

    • URL: POST /token
    • 如果用戶啟用了 2FA,會返回 2fa_required 錯誤及 X-2FA 標頭。
  2. 驗證 OTP:

    • URL: POST /api/twofactor/validate
    • 提交 UserId 和 OTP,獲取完整的 Bearer Token。
  3. 使用 Token 訪問受保護的 API。


6. 改進建議

  1. 密碼哈希
    使用安全哈希(如 PBKDF2 或 BCrypt)存儲用戶密碼。

  2. HTTPS 強制
    生產環境中強制使用 HTTPS 確保數據傳輸安全。

  3. Token 有效期管理
    配置 Refresh Token 或縮短 Access Token 有效期以提升安全性。

沒有留言:

張貼留言