實作零信任架構的 .NET Core Web API 教學 前言 在現代應用程式開發中,安全性是一個不能忽視的重要議題。零信任(Zero Trust)安全模型提供了一個「永不信任,始終驗證」的架構方針,特別適合用於建構安全的 Web API。本文將詳細說明如何在 .NET Core Web API 中實作零信任架構。
零信任架構核心概念 零信任架構建立在以下幾個核心原則上:
永遠驗證 - 對每個請求都進行身分驗證
最小權限 - 只給予必要的存取權限
假設破壞 - 假設網路環境已被入侵
全程加密 - 所有通訊都需要加密
持續監控 - 記錄與分析所有活動
專案設定 首先建立一個新的 .NET Core Web API 專案:
1 2 dotnet new webapi -n ZeroTrustApi cd ZeroTrustApi
安裝必要的 NuGet 套件: 1 2 3 4 dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package AspNetCoreRateLimit
API檔案與安全測試 1. Swagger 檔案設定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1" , new OpenApiInfo { Title = "Zero Trust API" , Version = "v1" , Description = "實作零信任架構的 Web API" , Contact = new OpenApiContact { Name = "Your Name" , Email = "your.email@domain.com" } }); c.AddSecurityDefinition("Bearer" , new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "bearer" , BearerFormat = "JWT" , Description = "請在下方輸入 JWT Token" }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty<string >() } }); });
2. 單元測試設定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class SecureControllerTests { private readonly SecureController _controller; private readonly Mock<ILogger<SecureController>> _loggerMock; public SecureControllerTests () { _loggerMock = new Mock<ILogger<SecureController>>(); _controller = new SecureController(_loggerMock.Object); var claims = new [] { new Claim(ClaimTypes.Name, "testuser" ), new Claim(ClaimTypes.Role, "Admin" ) }; var identity = new ClaimsIdentity(claims, "Test" ); var claimsPrincipal = new ClaimsPrincipal(identity); _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = claimsPrincipal } }; } [Fact ] public async Task Get_AuthenticatedUser_ReturnsOkResult () { var result = await _controller.Get(); Assert.IsType<OkObjectResult>(result); } }
實作身分驗證 1. JWT 認證設定 在 Program.cs 中加入 JWT 認證:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 var builder = WebApplication.CreateBuilder(args );builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true , ValidateAudience = true , ValidateLifetime = true , ValidateIssuerSigningKey = true , ValidIssuer = builder.Configuration["Jwt:Issuer" ], ValidAudience = builder.Configuration["Jwt:Audience" ], ClockSkew = TimeSpan.Zero, RequireExpirationTime = true , ValidateLifetime = true , IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key" ])), TokenDecryptionKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:EncryptionKey" ])) }; options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { if (context.Exception.GetType() == typeof (SecurityTokenExpiredException)) { context.Response.Headers.Add("Token-Expired" , "true" ); } return Task.CompletedTask; } }; });
2. 使用者身分模型 1 2 3 4 5 6 public class ApplicationUser : IdentityUser { public string FirstName { get ; set ; } public string LastName { get ; set ; } public DateTime LastLoginTime { get ; set ; } }
3. 資料庫上下文 1 2 3 4 5 6 7 public class ApplicationDbContext : IdentityDbContext <ApplicationUser >{ public ApplicationDbContext (DbContextOptions<ApplicationDbContext> options ) : base (options ) { } }
API 存取控制 1. 實作請求限流 1 2 3 builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimit" )); builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>(); builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
2. 自訂授權政策 1 2 3 4 5 6 7 8 builder.Services.AddAuthorization(options => { options.AddPolicy("RequireAdminRole" , policy => policy.RequireRole("Admin" )); options.AddPolicy("RequireApiAccess" , policy => policy.RequireClaim("scope" , "api_access" )); });
API 端點安全性 1. 安全的控制器範例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 [ApiController ] [Route("api/[controller]" ) ] [Authorize ] public class SecureController : ControllerBase { private readonly ILogger<SecureController> _logger; public SecureController (ILogger<SecureController> logger ) { _logger = logger; } [HttpGet ] [ProducesResponseType(StatusCodes.Status200OK) ] [ProducesResponseType(StatusCodes.Status401Unauthorized) ] public async Task<IActionResult> Get () { try { _logger.LogInformation( "Access attempt by user {User} at {Time}" , User.Identity.Name, DateTime.UtcNow); if (!ValidateRequest()) { return Unauthorized(); } return Ok(new { message = "Secure data" }); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while processing request" ); return StatusCode(500 ); } } private bool ValidateRequest () { return true ; } }
2. 全域錯誤處理中介軟體 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class SecurityHeadersMiddleware { private readonly RequestDelegate _next; public SecurityHeadersMiddleware (RequestDelegate next ) { _next = next; } public async Task InvokeAsync (HttpContext context ) { context.Response.Headers.Add("X-Frame-Options" , "DENY" ); context.Response.Headers.Add("X-Content-Type-Options" , "nosniff" ); context.Response.Headers.Add("X-XSS-Protection" , "1; mode=block" ); context.Response.Headers.Add("Referrer-Policy" , "strict-origin-when-cross-origin" ); context.Response.Headers.Add("Content-Security-Policy" , "default-src 'self'; script-src 'self'" ); await _next(context); } }
加密與安全通訊 1. HTTPS 設定 在 Program.cs 中加入:
1 2 3 4 var app = builder.Build();app.UseHttpsRedirection(); app.UseHsts();
2. 敏感資料加密服務 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public interface IEncryptionService { string Encrypt (string plainText ) ; string Decrypt (string cipherText ) ; } public class EncryptionService : IEncryptionService { private readonly string _key; private readonly string _iv; public EncryptionService (IConfiguration configuration ) { _key = configuration["Encryption:Key" ]; _iv = configuration["Encryption:IV" ]; } public string Encrypt (string plainText ) { using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(_key); aes.IV = Encoding.UTF8.GetBytes(_iv); ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (MemoryStream msEncrypt = new MemoryStream()) { using (CryptoStream csEncrypt = new CryptoStream( msEncrypt, encryptor, CryptoStreamMode.Write)) using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) { swEncrypt.Write(plainText); } return Convert.ToBase64String(msEncrypt.ToArray()); } } } public string Decrypt (string cipherText ) { return string .Empty; } }
日誌記錄與監控 1. 設定結構化日誌 1 2 3 4 5 6 7 8 9 builder.Logging.ClearProviders(); builder.Logging.AddConsole(); builder.Logging.AddDebug(); builder.Logging.AddEventLog(); builder.Logging.AddSerilog(new LoggerConfiguration() .WriteTo.File("logs/api-.txt" , rollingInterval: RollingInterval.Day) .CreateLogger());
2. 稽核日誌中介軟體 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class AuditLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public AuditLoggingMiddleware ( RequestDelegate next, ILogger<AuditLoggingMiddleware> logger ) { _next = next; _logger = logger; } public async Task InvokeAsync (HttpContext context ) { var auditLog = new { Timestamp = DateTime.UtcNow, RequestPath = context.Request.Path, RequestMethod = context.Request.Method, UserAgent = context.Request.Headers["User-Agent" ].ToString(), UserIp = context.Connection.RemoteIpAddress?.ToString(), UserId = context.User.Identity?.Name }; _logger.LogInformation( "API Request: {@AuditLog}" , auditLog); await _next(context); } }
跨域資源共用(CORS)設定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 builder.Services.AddCors(options => { options.AddPolicy("SecurePolicy" , policy => { policy .WithOrigins( "https://yourdomain.com" , "https://api.yourdomain.com" ) .WithMethods("GET" , "POST" , "PUT" , "DELETE" ) .WithHeaders("Authorization" , "Content-Type" ) .AllowCredentials() .SetPreflightMaxAge(TimeSpan.FromMinutes(10 )); }); }); app.UseCors("SecurePolicy" );
健康檢查設定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 builder.Services.AddHealthChecks() .AddDbContextCheck<ApplicationDbContext>() .AddUrlGroup(new Uri("https://dependent-service.com/health" )) .AddCheck("DiskSpace" , new DiskSpaceHealthCheck(1024 )) .AddCheck<CustomHealthCheck>("Custom" ); public class CustomHealthCheck : IHealthCheck { public Task<HealthCheckResult> CheckHealthAsync ( HealthCheckContext context, CancellationToken cancellationToken = default ) { try { return Task.FromResult(HealthCheckResult.Healthy()); } catch (Exception ex) { return Task.FromResult( HealthCheckResult.Unhealthy(ex.Message)); } } } app.MapHealthChecks("/health" , new HealthCheckOptions { ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse });
容器化與部署 1. Docker 設定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS baseWORKDIR /app EXPOSE 80 EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:7.0 AS buildWORKDIR /src COPY ["ZeroTrustApi.csproj" , "./" ] RUN dotnet restore "ZeroTrustApi.csproj" COPY . . RUN dotnet build "ZeroTrustApi.csproj" -c Release -o /app/build FROM build AS publishRUN dotnet publish "ZeroTrustApi.csproj" -c Release -o /app/publish FROM base AS finalWORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet" , "ZeroTrustApi.dll" ]
2. CI/CD Pipeline (GitHub Actions) 這是基本範例,實際使用時應該根據部署環境需求調整設定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 name: .NET Core CI/CD on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: '7.0.x' - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --configuration Release --no-restore - name: Test run: dotnet test --no-restore --verbosity normal - name: Publish run: dotnet publish -c Release -o published - name: Build and push Docker image uses: docker/build-push-action@v2 with: context: . push: true tags: yourdockerrepo/zerotrust-api:latest
APM 與監控 1. Application Insights 設定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 builder.Services.AddApplicationInsightsTelemetry(options => { options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString" ]; options.EnableAdaptiveSampling = false ; options.EnableDebugLogger = true ; }); public class CustomTelemetryInitializer : ITelemetryInitializer { public void Initialize (ITelemetry telemetry ) { telemetry.Context.Cloud.RoleName = "ZeroTrustApi" ; telemetry.Context.Cloud.RoleInstance = Environment.MachineName; if (telemetry is RequestTelemetry request) { request.Properties["CustomProperty" ] = "CustomValue" ; } } }
2. 效能監控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class PerformanceMonitoringMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly TelemetryClient _telemetryClient; public PerformanceMonitoringMiddleware ( RequestDelegate next, ILogger<PerformanceMonitoringMiddleware> logger, TelemetryClient telemetryClient ) { _next = next; _logger = logger; _telemetryClient = telemetryClient; } public async Task InvokeAsync (HttpContext context ) { var sw = Stopwatch.StartNew(); try { await _next(context); } finally { sw.Stop(); var elapsed = sw.ElapsedMilliseconds; _telemetryClient.TrackMetric( "RequestDuration" , elapsed, new Dictionary<string , string > { {"Path" , context.Request.Path}, {"Method" , context.Request.Method} }); if (elapsed > 1000 ) { _logger.LogWarning( "Long running request: {Path} took {Elapsed}ms" , context.Request.Path, elapsed); } } } }
最佳實踐建議
定期更新套件 :
使用 dotnet list package --vulnerable 檢查漏洞
設定自動化更新提醒
設定檔管理 :
使用 Azure Key Vault 或其他安全的祕密管理服務
不要在程式碼中硬編碼任何祕密
API 版本控制 :
效能與安全性平衡 :
持續監控 :
災難復原計畫 :
定期備份資料庫和設定檔
建立自動化還原程序
設定異地備援機制
進行定期的災難復原演練
API 限流設定範例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 builder.Services.Configure<IpRateLimitOptions>(options => { options.GeneralRules = new List<RateLimitRule> { new RateLimitRule { Endpoint = "*" , Period = "1m" , Limit = 30 }, new RateLimitRule { Endpoint = "*" , Period = "1h" , Limit = 500 } }; });
密碼雜湊設定 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 builder.Services.Configure<IdentityOptions>(options => { options.Password.RequireDigit = true ; options.Password.RequiredLength = 8 ; options.Password.RequireNonAlphanumeric = true ; options.Password.RequireUppercase = true ; options.Password.RequireLowercase = true ; options.Password.HasherOptions = new PasswordHasherOptions { IterationCount = 100000 }; });
結論 實作零信任架構需要全面性的考量,從身分驗證、授權、加密到監控都需要謹慎規劃。本文提供的範例程式碼和建議可作為建置安全 API 的起點,但記住安全是一個持續進行的過程,需要定期檢視和更新。
參考資料 [
* [ASP.NET Core 安全性文件](https://docs.microsoft.com/zh-tw/aspnet/core/security/)
* [OWASP Top 10 API Security Risks](https://owasp.org/www-project-api-security/)
* [Microsoft Identity Platform](https://docs.microsoft.com/zh-tw/azure/active-directory/develop/)
* [ASP.NET Core 應用程式監控](https://docs.microsoft.com/zh-tw/azure/azure-monitor/app/asp-net-core)
* [Docker 容器化最佳實踐](https://docs.docker.com/develop/develop-images/dockerfile\_best-practices/)
* [.NET Core 中的密碼雜湊](https://docs.microsoft.com/zh-tw/aspnet/core/security/data-protection/consumer-apis/password-hashing)
]