實作零信任架構的 .NET Core Web API 教學

前言

在現代應用程式開發中,安全性是一個不能忽視的重要議題。零信任(Zero Trust)安全模型提供了一個「永不信任,始終驗證」的架構方針,特別適合用於建構安全的 Web API。本文將詳細說明如何在 .NET Core Web API 中實作零信任架構。

零信任架構核心概念

零信任架構建立在以下幾個核心原則上:

  1. 永遠驗證 - 對每個請求都進行身分驗證
  2. 最小權限 - 只給予必要的存取權限
  3. 假設破壞 - 假設網路環境已被入侵
  4. 全程加密 - 所有通訊都需要加密
  5. 持續監控 - 記錄與分析所有活動

專案設定

首先建立一個新的 .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"
}
});

// JWT 驗證設定
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
// SecureControllerTests.cs
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()
{
// Arrange

// Act
var result = await _controller.Get();

// Assert
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"],

// Token 存活時間設定
ClockSkew = TimeSpan.Zero, // 避免時間偏差
RequireExpirationTime = true,
ValidateLifetime = true,

// Token 加密金鑰設定
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
TokenDecryptionKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:EncryptionKey"]))
};
// Token 過期事件處理
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);
}

// 注意: 在生產環境中,應該為每次加密操作產生新的 IV
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();

// 加入 Serilog
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") // 允許的 HTTP 方法
.WithHeaders("Authorization", "Content-Type") // 允許的標頭
.AllowCredentials() // 允許認證資訊
.SetPreflightMaxAge(TimeSpan.FromMinutes(10)); // 預檢請求快取時間
});
});

// 在 Program.cs 中啟用 CORS
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
// 註冊健康檢查服務
// DiskSpaceHealthCheck 和 CustomHealthCheck 僅為示範用途
// 實際使用時應根據專案需求實作完整的檢查邏輯
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));
}
}
}

// 在 Program.cs 中設定健康檢查端點
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 base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["ZeroTrustApi.csproj", "./"]
RUN dotnet restore "ZeroTrustApi.csproj"
COPY . .
RUN dotnet build "ZeroTrustApi.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "ZeroTrustApi.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /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
// 在 Program.cs 中加入
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);
}
}
}
}

最佳實踐建議

  1. 定期更新套件

    • 使用 dotnet list package --vulnerable 檢查漏洞
    • 設定自動化更新提醒
  2. 設定檔管理

    • 使用 Azure Key Vault 或其他安全的祕密管理服務
    • 不要在程式碼中硬編碼任何祕密
  3. API 版本控制

    • 實作 API 版本管理
    • 提供版本降級機制
  4. 效能與安全性平衡

    • 實作快取機制
    • 使用適當的請求限流設定
  5. 持續監控

    • 設定警報機制
    • 定期審查日誌
    • 進行安全性掃描
  6. 災難復原計畫

    • 定期備份資料庫和設定檔
    • 建立自動化還原程序
    • 設定異地備援機制
    • 進行定期的災難復原演練
  7. 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. 密碼雜湊設定
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 // PBKDF2 迭代次數
};
});

結論

實作零信任架構需要全面性的考量,從身分驗證、授權、加密到監控都需要謹慎規劃。本文提供的範例程式碼和建議可作為建置安全 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)